PyCon US 2021: FastAPI Seems Good, so Why Don’t We Build Something Similar For Flask?

Meta:

这是我第一次参加 PyCon US,也是第一次做英文演讲。也许会有一些语法和发音的问题,不过因为时间有限,我已经尽力了……这次开始准备的比较晚,演讲基本是在三天内完成的,演讲前一天花了一整天时间准备,从早上一直到第二天两点。总耗时 18 小时 41 分钟。相比之下,同样是五分钟的闪电演讲,在 PyCon China 2019 上海场的闪电演讲《Python 虚拟环境和依赖管理工具大乱斗》却花费了接近 40 个小时(大部分时间都用来做幻灯片了)。

这个演讲本来是一个比较深入的主题演讲,不过因为主题演讲申请惨遭淘汰,只好换成闪电演讲。演讲灵感来自前段时间的那篇《请不要把 Flask 和 FastAPI 放到一起比较》。

尽管是预录制演讲,但是不知道主办方哪里没弄好,实际大会直播的时候音画不同步,卡顿很严重(见官方发布的演讲视频)。

开始翻译 Flask 文档

我在上周开始策划 Flask 文档翻译的事,制定了大致的翻译流程,创建了翻译协调仓库翻译模板仓库。中文翻译作为这个流程的试水项目,在上周末正式(静悄悄)启动。

启动中文翻译项目的第一步是把 logo 里的标语「web development, one drop at a time」翻译成中文,这让我想起来一个也许冷也许不冷的冷知识。很多人知道 Flask 的名字是对 Bottle 的双关语——另一个容器/另一个 Python Web 框架。那为什么 Python Web 框架爱用容器做名字?其实是因为这些 Python Web 框架都是基于 WSGI 规范实现的,而 WSGI 不知从哪天起流行被读作 Whiskey(威士忌),所以这两个 Python Web 框架才叫做 Bottle 和 Flask,所以才有「Web 开发,一次一滴」。

欢迎在这些 discussions 中分别投票选出最佳翻译词汇、中文 logo 字体和中文标语。

回到正题。如果你对 Flask 足够熟悉,并且擅长翻译,欢迎参与进来!翻译通过 GitHub 进行协作(greyli/flask-docs-zh)。除了翻译文档,我们也需要有丰富翻译经验的朋友来审阅提交的翻译,欢迎在 GitHub 上关注(点 Watch 按钮)这个项目以接收新增翻译提醒。

下面是参与翻译之前要了解的三件事:

  1. 请务必完整阅读 Contributing Guide 了解翻译流程,然后阅读这个 issue 了解翻译要求。
  2. 需要强调的是,完成一章的翻译要提交两个 PR:第一个 PR 在 README 中对应的章节条目后添加自己的用户名以认领章节;第二个 PR 翻译对应的 .po 文件并勾选完成的章节条目。先认领,再翻译,一次只认领一章。
  3. 不要改动任何 ReStructuredText 标记、变量/类/函数/方法名称、URL 等。

如果有其他问题和相关想法,请创建 discussion 发起讨论;如果对翻译流程和项目设置有改进建议,或是发现了翻译错误和笔误,请创建 issue 反馈。

这个项目在后期会转移到 FlaskCWG 组织,并在翻译完成后链接到 Flask 官方文档。

Flask 2.0 版本发布

Flask 以及 Flask 依赖的 5 个 Pallets 项目都在今天发布了新的主版本(下面的链接指向各个项目的主版本变动日志):

​你可以使用下面的命令更新 Flask:

 pip install -U flask

如果你使用的国内 PyPI 镜像还没有同步最新版本,可以通过下面的命令临时切换到官方 PyPI 源:

 pip install -U flask -i https://pypi.org/simple/

这篇文章会介绍一些 Flask 新增的特性,完整的变动可以参考上面各个项目的变动日志。

三个核心特性

嵌套蓝本(#3923

对于一个比较大的项目,一般会使用蓝本来组织不同的模块。而如果你的项目非常大,那么嵌套蓝本就可以派上用场了。借助嵌套蓝本支持,你可以在某个蓝本之上再创建多个子蓝本,对项目进行多层模块化组织(而且支持无限嵌套,你可以嵌套很多层):

 parent = Blueprint("parent", __name__)  # 创建父蓝本
 child = Blueprint("child", __name__)  # 创建子蓝本
 parent.register_blueprint(child, url_prefix="/child")  # 把子蓝本注册到父蓝本上
 app.register_blueprint(parent, url_prefix="/parent")  # 把父蓝本注册到程序实例上

这样在生成子蓝本的 URL 时需要传入完整的端点链:

 url_for('parent.child.create')
 /parent/child/create

这个特性来源于一个 2012 年创建的 feature request issue

基本的 async/await 支持(#3412

Flask 2.0 带来了基本的异步支持,现在你可以定义异步视图(以及异步错误处理函数、异步请求钩子函数):

 import asyncio
 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 async def say_hello():
     await asyncio.sleep(1)
     return {'message': 'Hello!'}

注意要先安装额外依赖:

 pip install -U flask[async]

顺便说一句,如果你在 Windows 上使用 Python 3.8,那么会有一个来自 Python 或 asgiref 的 bug 导致出错:ValueError: set_wakeup_fd only works in main thread。可以通过下面两种方式(任选一种)处理(具体参考这个 SO 回答):

  • 升级到 Python 3.9
  • 在你的入口脚本顶部添加临时修复代码:
 # top of the file
 import sys, asyncio
 ​
 if sys.platform == "win32" and (3, 8, 0) <= sys.version_info < (3, 9, 0)::
     asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

不过目前只是一个基于 asgiref 的异步实现,作为异步支持的第一步,后续还会进行更多的优化和改进,更多相关信息可以参考文档

快捷路由装饰器(#3907

新增了下面的快捷路由装饰器:

  • app.get()
  • app.post()
  • app.delete()
  • app.put()
  • app.patch()

举例来说,使用 app.post() 等同于 app.route(methods=['POST'])

 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.post('/')
 def index():
     return {'message': 'Hello!'}

注意不要在这些快捷装饰器里传入 methods 参数。如果需要在单个视图处理多个方法的请求,使用 app.route()

我在某次 pallets 会议上提议添加这些装饰器时一开始是被拒绝的,后来 Phil Jones 创建了 #3907 经过二次讨论后才最终合并(被拒绝后我就把当时正在开发的 APIFlask 从扩展改成了继承 Flask 基类的框架,然后加了这些装饰器)。

我添加的三个特性

修复执行 flask run 找不到程序的奇怪设定(#3560

这个像 bug 又像是 feature 的设定我在《一个困扰我两年的 Flask「Bug」》里详细说过,最终终于在两年后修复了。

支持在 .flaskenv 和 .env 文件里写中文(#3932

我在《Flask Web 开发实战》第一章介绍 .flaskenv 文件时给了一个示例,演示如何添加注释:

SOME_VAR=1
# 这是注释

但这个示例没有实际测试……加了中文其实会报错,后来有两次收到读者反馈,最终终于在三年后修复了。

为文档添加命令切换面板(#3714

这个或许算不上特性,不过在我看来是对用户非常友好的变动。除了个别不需要区分操作系统和命令行程序的命令外,我给文档里所有的命令添加了支持切换 Bash/CMD/Powershell 以及 macOS/Linux/Windows 的切换面板(面板的样式后续会有一些优化):

其他的有用特性

  • 优化了浏览器缓存控制,对 CSS、图片等静态文件做出的变动会在程序重载后立刻更新,不再需要手动清除页面缓存。
  • Werkzeug 的 multipart 解析(尤其是大文件上传处理)性能提高了 15 倍。
  • 配置对象增加 Config.from_file() 方法支持从任意文件加载器导入配置(比如 toml.loadjson.load),未来会取代 Config.from_json() 方法。
  • 在使用环境变量 FLASK_APP 指定工厂函数时支持传入关键字参数。
  • flask shell 支持 tab 和历史补全(需要安装 readline)。
  • CLI 系统优化了找不到程序时的错误处理和错误输出显示,同时修正了 Windows 上的命令行颜色输出。

破坏性变动

主要的破坏性变动(breaking change)是意外的把 send_from_directory() 函数的第二个参数名称直接由 filename 重命名为 path,将会在 2.0.1 加回来(#4019)。

另外 send_file() 函数的三个参数也进行了重命名(旧名称将在 2.1.0 移除):

  • attachment_filename -> download_name
  • cache_timeout -> max_age
  • add_etags -> etag

其他重要变化

  • 不再支持 Python 2 和 Python 3.5。
  • 所有 Pallets 项目都添加了 type hinting,这意味着更好的 IDE 自动补全体验。
  • 所有仓库的主分支由 master 改为 main。如果你在本地克隆了 Flask 等仓库,可以使用下面的命令来更新:
 git branch -m master main
 git fetch origin
 git branch -u origin/main main
 git remote set-head origin -a

感谢支持

这次更新对于整个 Pallets 项目来说是一个新的里程碑。接下来还有许多事情要做:FlaskCon 2021 正在准备中,新建立的 Flask 社区工作小组(Flask Community Work Group)正在进行 Flask 文档翻译(如果你对中文翻译感兴趣,可以订阅这个讨论)、被遗弃扩展收容计划(这是我一直想做的事情)等等。感谢支持,敬请期待!

欢迎通过下列途径关注 Pallets 项目:

相关文章:

请不要把 Flask 和 FastAPI 放到一起比较

The war has begun. Let’s end the war between Flask and FastAPI.

去年在知乎上看到一篇文章,后来想找却找不到了,最后通过关键词「5 分钟」和「四年」在掘金上找到了原文——《用它5分钟以后,我放弃用了四年的 Flask》。作者先是给出了一个手动用 if 验证请求数据的 Flask 视图函数:

@app.route('/insert', methods=['POST'])
def insert():
    info = request.json
    name = info.get('name', '')
    if not name:
        return {'success': False, 'msg': 'name 参数不可省略,不可为空!'}
    age = info.get('age', 0)
    if not isinstance(age, int):
        return {'success': False, 'msg': 'age参数不是数字!'}
    age_after_10_years = age + 10
    msg = f'此人名叫:{name},10年后,此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

然后给出了一个使用 Pydantic 编写数据模型类 + FastAPI 路径操作函数的代码:

class People(BaseModel):
    name: str
    age: int
    address: str
    salary: float
    
@app.post('/insert')
def insert(people: People):
    age_after_10_years = people.age + 10
    msg = f'此人名字叫做:{people.name},十年后此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

通过这两段代码进行对比,文章作者得出结论「我用了 Flask 四年,但在使用了5分钟 FastApi 以后,我决定以后不再使用 Flask 了」。你确定你用了四年的是 Flask 而不是 Flash?在此后的很长一段时间里,经常看到类似的对比和结论,所以我打算用这篇文章来澄清这个非常普遍的误解。如果看到有人把 FlaskFastAPI 放到一起比较,请把这篇文章的链接丢过去。

利益相关:笔者系 Flask 维护者和 APIFlask 作者。本文观点仅代表个人看法,与 Pallets 团队无关。

为什么 Flask 和 FastAPI 不能放到一起比较?

我在此前介绍 APIFlask 的文章里一开始就提到过这件事情:

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?

前两天看了 Reddit 上 Flask 2.0rc 帖子下的很多回复,预感到在 Flask 2.0 发布后在中文网络上也许会出现许多类似的问题和讨论,所以想着还是值得单独写一篇文章来谈这件事。

受那个 Reddit 主题里某个评论启发,想到一个更好的比喻: Flask 和 FastAPI 就像是苹果和橙汁。你不能把苹果和添加了更多蔗糖和添加剂的橙汁放在一起比较哪一个更甜。苹果(Flask)应该和橙子(Starlette)比较,橙汁(FastAPI)当然也应该和苹果汁(基于 Flask 的 Web API 框架)进行比较。也就是说,Flask 是一个通用型框架,和 FastAPI 依赖的 Starlette 一样,而 FastAPI 是添加 Web API 支持的二次框架。因此,Flask 应该和 FastAPI 所依赖的 Starlette 进行比较,而 FastAPI 应该和基于 Flask 的 Web API 框架进行比较。

同理,FastAPI 也不能和 Django、Tornado、web2py、Bottle、CherryPy、Pyramid、Sanic 等通用型 Web 框架比较。

更合理的比较对象是什么?

既然「FastAPI 应该和基于 Flask 的 Web API 框架比较」,那么合适的比较对象有哪些?Flask-RESTXFlask-Rebarflask-apispecflask-smorestFlask-RESTfulAPIFairy 这些虽然试图做成框架,但在具体实现上仍然是 Flask 扩展,所以真正公平合理的比较对象是:

这三个框架都是基于 Flask 实现的 Web API 框架。不过 Eve 没有内置 OpenAPI 支持,而 Connexion 是一个 Spec-First 框架,也就是 OpenAPI spec 优先(先编写 spec,然后生成项目基础代码进行开发)的框架。因此,尽管 APIFlask 只是一个刚发布没多久的实验项目,但它是最适合和 FastAPI 进行比较的项目。

FastAPI 和 APIFlask 有着同样的组成结构,都是作为一层胶水粘起来一个 Web 框架和一个数据序列化/验证库,然后加一点 Web API 功能支持:

在主要功能上也有很多交集:

  • 自动反序列化和验证请求数据,自动生成错误响应
  • 自动格式化和序列化响应数据
  • 自动生成 OpenAPI specification 文件
  • 自动生成交互式 API 文档

当然,相对于发布已经三年的 FastAPI,APIFlask 还只是一个三个月大的新项目,还有很多地方需要改进和完善。如果你对 APIFlask 感兴趣的话,可以在这篇文章了解详细介绍或是在它的 GitHub 仓库查看源码。

为什么会有这样的误解?

为什么会有那么多人把 FastAPI 和 Flask 放到一起比较?在我看来有三个原因:

首先是 FastAPI 采用了和 Flask 类似的装饰器路由,很容易让人联想到 Flask。

二是 FastAPI 没有给它的项目构成做足够的说明。如果 FastAPI 在其介绍的第一句就加上「based on Starlette and Pydantic」而不是放到 Requirements 部分才提及,这样也许会让 StarlettePydantic 获得更多应有的关注,也就不会有这么多人拿 Flask 和它比较。

再就是 FastAPI 的推介者对 FastAPI 了解的不够多。不清楚它的项目结构,自然就不了解它和其他 Web 框架的区别。而介绍时为了带来足够的吸引力,推介者常常选择拉上和用户量足够大的「竞品」进行对比这种方式,所以才会有大量类似本文开头提到的那种文章。这些文章甚至完全没有提及 Starlette 和 Pydantic,只是一味强调「这是一个超高性能的、碾压 Flask 的框架」。

FastAPI 不是银弹

FastAPI 只是说它能把「功能开发速度提升约 200% 至 300%」,距离十倍还差了一些,自然不能算是银弹。但是这两年看到了很多对 FastAPI 的盲目吹捧,仿佛 FastAPI 就是完美的解决方案。这也许都要归功于 FastAPI 在其 README 和文档里「大胆」的措辞和承诺以及不厌其烦的特性介绍。举例来说,如果它只是说「Very high performance, it is comparable to some frameworks in Go and NodeJS」,那么用户也许就会认为「和 Go/NodeJS 有一拼」,但是说成「Very high performance, on par with NodeJS and Go」,那么用户就会想「和 Go/NodeJS 不相上下」(以至于中文翻译是「可与 NodeJS 和 Go 比肩的极高性能」),这当然会带来一些争议。再比如,如果只是说「double or triple the development speed」,那么用户大概会想「效率还不错」,但是它用了「Increase the speed to develop features by about 200% to 300%」,数学不好的用户就会惊呼「哇,提高 30 倍开发效率」。同时这种营销带来的狂热用户,也很容易被煽动去给 Python Steering Council 施加压力——在 Python 3.10 快要发布的时候突然跳出来要求撤销一个 PEP。

这种营销至上的项目运营方式带来的直接后果是糟糕的维护状态。FastAPI 似乎在慢慢变成一个翻译项目,在代码里的文档字符串都还没写的情况下,其用户文档就已经开始扩展到十几种语言。而这十几种语言的翻译和项目源码都放在同一个仓库里,这导致所有开启的近 300 个 PR 里有过半是翻译。issue tracker 和 discussion 不做区分的使用,这导致所有开启的近 600 个 issue 里有九成是提问)。我在此前创建过一个 issue 来反馈这个问题,但并没有得到回应。另外凭个人喜好来说,在每个 commit 信息里都加上 emoji 并不可爱。而每一个 commit 都要触发 bot 更新 changelog,带来的是一份丑陋的 commit 历史(所有 commit 里有三分之一是在更新 changelog)。这些也许是发展社区或是让项目看起来很活跃的有效方法,但很显然这带来的是建立在混乱之上的虚假繁荣。

另外,许多推介文章都会在最后贴出来一张 benchmark 截图来证明 FastAPI 有多么 fast,但是完全不会提及的是:贴出来的 benchmark 对于开发生产应用来说有多大的意义?这里的 benchmark 背后有没有任何的 hacky?异步是否等同于高性能还是要看情况?框架本身的性能在一个请求处理流程中占多大的影响?asyncio 的生态怎么样?

从长远看这些大都是一些临时问题,而且 FastAPI 作者已经开始全职开发开源项目,这些问题在未来应该都会慢慢得到改善。指出这些是希望更多的人可以客观看待 FastAPI,吹捧并不能让一个东西变得更好,参与开发、介绍用法和回答社区提问是比盲目吹捧更有意义的事情。我当然期待 FastAPI 能够越来越好,也期待看到有更多优秀的 Python 框架出现,但我不喜欢过度炒作、盲目的吹捧和错误的对比。

P.S. Flask 2.0 版本即将发布

顺便说一句,Flask、Werkzeug、Jinja 等 Pallets 项目都将在 5 月 11 日(PyCon 开始)之前发布下一个主版本,主要包含下面这些新特性(完整变动见 changelog):

  • Flask、Werkzeug 和 Click 添加了 type hints
  • Flask 实现了有限的 async/await 支持
  • Flask 支持嵌套蓝本
  • Flask 添加了快捷版本的 route 装饰器(即 @app.get@app.post 等)
  • Flask 支持在视图类上使用 route 装饰器(我前两天刚提交了一个 PR 来实现这个功能,暂时不确定是否会被合并,在这先提前说一下 :p)
  • Werkzeug 的 multipart 解析性能大幅提升

你可以使用下面的命令安装 Flask 2.0.0rc 版本进行测试:

 $ pip install --pre flask

欢迎反馈 bug 和提改进建议。关于 2.0 版本的详细介绍将会在发布后另外撰文说明。

延伸阅读

2020 年总结

2021 年已经过去四个月了,每次到某个重要的时间节点都想完成这篇 2020 年总结,结果总是有各种事情挤占时间,所以错过了元旦、错过了大年初一、错过了 26 岁生日、错过了植树节和愚人节,最后来到这一天。

照例,先从好坏两方面总结一下。

Good:

Bad:

  • 尝试翻译一本英文书,但是试译稿没有通过编辑的质量要求
  • 没能按计划完成新书(延期两年了)
  • 组织「编程一小时」活动失败
  • 一次失败的外包合作
  • 玩太多游戏

很高兴我的书和项目在这一年帮到了更多的人:

感谢留言

而且我的读者群已经扩展到小学五年级了(继上一次某位正在上初中的读者邀请我评审他用 Flask 做的博客程序后)。如果还有三年级或是一年级的读者小朋友,记得吱一声 :P

2020 年只写了 23 篇文章,花了比较多时间写的是下面这几篇:

相比之下,文章数量要比 2019 年少 40 篇,也许是因为这一年花了更多时间来挣钱(虽然最后没剩下多少)。年初做了一个糟糕的外包项目,烂摊子接手,慢慢明白转手的原因,最后以没有拿到尾款结束。这之后就更谨慎了,半年时间里只做了一个微信小程序的后端 API。

除此之外,第一次通过写文章挣到了钱——给 Twilio 博客写文章的稿费。第一篇文章是做一个协作白板,因为 SocketIO 有一个类似的示例程序,所以做程序加上写文章只花了 17 小时 34 分钟。但是第二篇文章就没那么幸运了,这一篇做一个演讲程序,因为 Twilio 文档写的不是很详细,所以卡在程序实现上很久,断断续续花了两个月,最终耗时 50 小时 24 分钟,时薪骤降。

今年的收入大部分都用作房租和生活开支了。买了一个很大的显示器(43 寸),不过并没有让写书进度加快……买了 Switch,玩了一段时间《喷射战士 2》(Splatoon 2,拒绝官方译名),偶尔还会换回一代玩,喜欢第一代 Carbon Roller Deco 和 Rainmaker 鱼头的无敌效果。

去年在年总结里说要重新开始记录时间花销,说到做到,2020 年记录在案的时间一共 8769 小时 48 分钟(99.8% 覆盖率):

时间花销饼图

P.S. 上面的分类是时间大类,其中娱乐包括游戏(491 小时 4 分钟)、电影电视剧(283 小时 39 分钟)和上网(269 小时 22 分钟);休息时间大多是不同事项之间的切换和走神时间;工作包括外包开发(332 小时 49 分钟)和开源项目(136 小时 47 分钟);写作包括写书(158 小时 50 分钟)和写文章(129 小时 58 分钟);​学习包括学编程(101 小时 16 分钟)和学英语(81 小时 52 分钟)。

下面是包含所有时间分类的年度柱状图(紫色系是睡觉和吃饭,黄色系是外包和演讲,绿色系是开源项目和写作,蓝色系是清洁活动,红色系是娱乐):

时间花销柱状图

这也很好的解释了为什么这一年没有太多成果:玩了太多游戏,在各种网站上转来转去,写书的时间甚至没超过写文章多少。

2021 年开始没有再记录详细的时间开支——完整记录时间太花费精力了。比起每天的详细时间花销数据,我更关心的其实是有没有达到设定的时间目标,所以现在改为只记录两类时间:一类是想要达到的时间目标,比如写作、编程和学习;另一类是需要限制的时间花销,比如娱乐(计划 2021 年娱乐花销控制在 500 小时以内,目前是 123 小时 44 分钟)。

年底和女友一起离开了南京,接下来整个 2021 年都会呆在家里,目标是完成下面三件事:

  1. 完成《Python Web API 设计与开发
  2. 完成《Flask Web 开发实战》第二版
  3. 完成 APIFlask 的 1.0 版本

APIFlask:一个基于 Flask 的 Web API 框架

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?为了让框架 PK 爱好者们有一个更公平的比较对象,从一份 APIFairy 0.6.2 版本的 fork 开始,我实现了一个基于 Flask 的 Web API 框架——APIFlask

blank

APIFlask 在 Flask 的基础上添加了更多 Web API 相关的功能支持,核心特性包括:

  • 更多方便的装饰器,比如 @app.input()@app.output()@app.get()@app.post() 等等
  • 自动反序列化和验证请求格式,当请求数据不符合模式类要求时,会自动生成包含错误详细信息的错误响应(基于 Webargs
  • 自动格式化和序列化响应数据,在定义好响应模式后,你可以直接在视图函数返回一个模型类对象,或是返回字典(基于 Marshmallow
  • 自动生成 OpenAPI Specification 文件,你可以把这个文件导入到 API 调试工具或是用来生成客户端代码(基于 APISpec
  • 自动生成交互式 API 文档,并自动为蓝本和视图设置对应的标签分类(基于 Swagger UI and Redoc
  • 自动为 HTTP 错误生成 JSON 格式的错误响应

下面是一个最基础的示例程序:

 from apiflask import APIFlask, Schema, abort
 from apiflask.fields import Integer, String
 from apiflask.validators import Length, OneOf
 ​
 app = APIFlask(__name__)  # 可以使用 title 和 version 参数来自定义 API 的名称和版本
 ​
 pets = [
     {
         'id': 0,
         'name': 'Kitty',
         'category': 'cat'
     },
     {
         'id': 1,
         'name': 'Coco',
         'category': 'dog'
     }
 ]
 ​
 # 定义一个请求数据模式类
 class PetInSchema(Schema):
     name = String(required=True, validate=Length(0, 10))  # 可以使用 description 参数添加字段描述
     category = String(required=True, validate=OneOf(['dog', 'cat']))
 ​
 # 定义一个响应数据模式类
 class PetOutSchema(Schema):
     id = Integer()
     name = String()
     category = String()
 ​
 ​
 @app.get('/pets/<int:pet_id>')
 @app.output(PetOutSchema)  # 使用 @output 装饰器标记响应数据模式
 def get_pet(pet_id):
     if pet_id > len(pets) - 1:
         abort(404)
     # 在真实程序里,你可以直接返回 ORM 模型类的实例,比如
     # return Pet.query.get(1)
     return pets[pet_id]
 ​
 ​
@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True))  # 使用 @input 装饰器标记请求数据模式
@app.output(PetOutSchema)
def update_pet(pet_id, json_data):  # 通过验证后的请求数据字典会注入到视图函数,默认参数名为 json_data
    if pet_id > len(pets) - 1:
        abort(404)
    for attr, value in json_data.items():
        pets[pet_id][attr] = value
    return pets[pet_id]

P.S. 你也可以使用类视图(class-based views),具体示例见这里

如果你想在你的电脑上运行这个示例,可以先用下面的命令安装 APIFlask(需要 Python 3.7 及以上版本,Flask 1.1 及以上版本):

Linux 和 macOS:

 $ pip3 install apiflask

Windows:

 > pip install apiflask

安装完成后把上面的代码保存到文件 app.py,然后执行下面的命令运行程序:

 $ flask run

现在你可以在浏览器访问 http://localhost:5000/docs 查看基于 Swagger UI 自动生成的交互式 API 文档:

Swagger UI

或者访问 http://localhost:5000/redoc 查看基于 Redoc 生成的 API 文档:

Redoc

访问 http://localhost:5000/openapi.json 可以获取自动生成的 OpenAPI spec 文件。

这个示例程序的完整版本可以在 https://github.com/greyli/apiflask/tree/master/examples 看到。如果你想了解更多用法,可以阅读文档中的基本用法一章(目前只有英文)。

APIFlask 只在 Flask 之上做了轻量级包装,所以你实际上仍然是在写一个 Flask 程序,所有 Flask 的特性都完全兼容。你只需要记住下面两处不同点:

  • 创建程序实例的时候使用 APIFlask 类(from apiflask import APIFlask)。
  • 创建蓝本实例的时候使用 APIBlueprint 类(from apiflask import APIBlueprint)。
  • 使用 apiflask.abort() 函数返回 JSON 格式的错误响应。

以下面的 Flask 程序为例:

 from flask import Flask, request, escape
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

迁移到 APIFlask 只需要改动两行代码:

 from apiflask import APIFlask  # 第一行
 from flask import request, escape
 ​
 app = APIFlask(__name__)  # 第二行
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

欢迎提出改进建议,报告 bug 或是分享其他任何相关的想法。你也可以在 GitHub 上创建 Issue,或是提交 PR 来改进它。谢谢!

组织「编程一小时」活动失败记

编程一小时(Hour of Code)」是由非盈利组织 Code.org 发起的公益活动,会在每年十二月初举办(计算机科学教育周)。简单来说,类似「地球一小时」活动,「编程一小时」活动就是在中小学引导学生们学习编程一小时。使用的材料大都是一些图形化的编程游戏(比如《我的世界》编程一小时教程),在游戏里使用类似 Scratch 的拖拽式编程逻辑块来操纵游戏中的人物做各种事情。

中国的「编程一小时」活动主要由微软和苹果这些大公司发起和主办。月初的时候,微软 MVP 项目组开始召集 MVP 做活动的讲师。南京有好几个学校需要讲师,但是我已经不在南京了,所以就想着在当地(徐州某县级市某镇某村)的中小学组织一次。在网上找了一些往年的活动记录,发现也有在农村组织的,想着应该没什么问题。

先是给高中的老师发了消息,没回;然后去村里的小学找校长,爬了三层楼没找到校长室,找到校长的电话打过去,校长说电脑坏了;发消息给初中的老师,老师说他们学校没有电脑;去了乡里的小学,门卫大叔说校长刚走;趁还没放学,我又赶到邻村的小学,老师告诉我要有教育局的通知才行。

「你好,我是微软的社区专家,想在你们学校举办一场免费的编程科普公益活动,叫做编程一小时。微软会提供相关的物料,我来做讲师,你们只要提供计算机教室和组织学生就可以了。」——听起来的确有些像骗子,也许对方以为我是要来推销少儿右脑开发心算术或是人工智能 5G 学习机。按理说我应该在这里就放弃了,但是我有点不甘心。尽管已经有 3 小时 48 分钟的沉没成本,我还是打算再试一下。

第二天,我花了五十分钟车程到达教育局,但是没到五分钟就出来了——没成想连教育局的门都进不去。教育局门卫室的保安问我找谁,我说:「我是……我来……」。保安又问:「你到底要找谁?要去哪个科室?」。我说:「我想来咨询一下关于进学校做免费编程培训的许可问题」。保安说:「没有这个科室,你想去哪个学校直接去找学校谈」。我说学校让我来找教育局,保安说来教育局没用得去找学校。在来回进行了几遍类似的对话后,保安有点生气,气冲冲地说「你这个人年纪轻轻怎么听不懂话呢!」。我也有点生气,但也只能气冲冲说句「好,谢谢!」。

既然到了市里,就想着再碰碰运气,所以紧接着我又去了市里最好的初中。这所初中的北门离公交站近,所以先去了北门。门卫师傅说校长室在南门,办事都走南门。因为校园不让进,所以我只能绕墙去南门(两门相距 530 米)。到了南门后,门卫师傅说校长不在,去开会了。我说那我找年级主任也行,师傅问我找哪一个年级主任……沟通(扯皮)半天后终于让我进了门卫室,让我等等看,说校长兴许一会儿来。一阵沉默。沉默之后我多介绍了一下自己,拿出来更多的宣传材料。门卫师傅指着玻璃桌面下的某个电话,让我打到校长室试一试,没人接。又是一段沉默。接着我又详细介绍了自己要做的事情。师傅提议让我去找副校长,副校长室在北门八号楼五楼,让我去北门。于是我绕墙回到北门,北门师傅问我找哪一位副校长?打过电话没有?预约了吗?我说南门师傅让我走北门找副校长,北门师傅说办事都走南门,没有预约校园不给外人进,让我走南门。如此重复几遍,直到师傅不耐烦了,我又绕墙回到南门。和南门师傅说着说着,师傅又指了桌面下某位副校长的手机让我打,打不通。继续沉默,沉默是今晚的南京市长江大桥。过了一会儿师傅又翻出另一本电话簿,找到校长的手机让我打,依然没人接。我看了看崭新的教学楼,告别门卫师傅,再绕墙走回北门,准备坐公交车离开这冰冷的城市。不幸的是,因为太晚了,回乡下的公交车已经没了,我只好斥巨资打车回家。

第三天一早我试着加上了副校长的微信,这是我离成功最近的一次。我们聊得很顺利,直到校长告诉我他们新教学楼刚建成,学校计算机教室还没装配……擦干眼泪,我又去了乡里的小学,上次去校长刚走,这次去校长刚好在门卫室。我听到了熟悉的答案:「这个活动挺好,不过校外的培训要有教育局的批准才能做,你得去教育局申请」。至此,耗时 7 小时 54 分的「编程一小时」活动策划正式宣告失败。

本来还想着也许会让某个小朋友喜欢上编程,若干年后他在写职业生涯回忆录的时候可能还会提到我——「走上这条路是因为小时候某一天不知道从哪里来了一个人在我们学校上了一堂编程启蒙课……」。现在也好,万一他以后因为做程序员得了颈椎病和脱发,可能还要怪到我头上呢。

2021/11/24 Update:我去联系了去年约好今年合作的那位校长。校长三天后回复我说计算机教室还没有安装好,我说可以进行「不插电编程」,校长说下午再聊。

PyCon China 2020: Python 技术名词发音指南

这是我在今年 PyCon China 的闪电演讲,不过没有去年那么闪电:

Django 怎么读?十个 Python 程序员也许会有十种读法。如果这个你刚好会读的话,那么还有 Werkzeug、SQLAlchemy、Gunicorn、PyPI、Bokeh、Huey…… 根据可靠来源,因为错误的发音,两个 Python 程序员在交谈时平均每小时会浪费三分钟时间用来互相确认和纠正名词发音。为了减少整个 Python 社区因为错误的技术名词发音产生更多的熵,这个演讲带来了一个可信度 95% 的 Python 技术名词发音指南。

这是我第一次做线上直播形式的演讲,在提交演讲申请之后我一直在想不知道会有多少人来听。也一直在担心 Windows 会不会蓝屏,国产软件会不会弹广告,猫会不会拔掉猫的电源……后来才知道可以提前录制,那就没什么好担心的了,所以你坐在屏幕前看到的我,当时也正坐在屏幕前看自己。

准备的过程中发现这可能是很容易带来争议的演讲:有些读音没有标准,有些读音我自己也读错好久,再加上有些人觉得发音无关紧要,有些人觉得错的人多了就成了正确发音,有些人还会守着错误发音扯到文化自信……不过没关系,反正这个演讲总能让台球再转起来一次,至少也可以短暂的让大家再一次正视错误发音问题。这个演讲也是我自己的一个发音纠正练习。

你也许觉得发音正不正确无关紧要,不,这其实是一件很重要的事情。拥有正确一致的发音可以让大家见面聊天更顺畅一点,不用停下来确认说的是同一个东西,在听播客和演讲的时候也不用被错误的发音打断思绪。而且有些错误的发音并不好听,比如「Windows 叉屁」……你只要稍稍花一点心力就可以避免让错误的发音变成主流发音,最后变成「事实上的正确发音」。一旦你不在乎这一点对错,生活里的很多事情都会慢慢变成无所谓和差不多另一方面,这份指南也希望可以给技术演讲和播客提供一个参考,如果 Python 培训班和各种九块九的网课感兴趣的话,这个指南或许可以放在第一课的最后作为延伸阅读的内容。只此两项,就可以避免错误发音大范围传播。

请从今天开始重视发音吧!


演讲准备备注:

  • 耗时:48h 35m(加上后期写这篇文章和上传视频的时间)
  • 幻灯片工具:Marpit
  • 一共录了 9 遍,演讲前一晚匆匆录了最后一版,很努力也才把时长压缩到 15 分钟(要求 10 分钟长度和最多五分钟偏差),有点赶。今天下午又重新录了一版,放轻松刚好 20 分钟。
  • 感觉线上演讲有点无聊……没有观众反馈,也没有紧张和兴奋的感觉。
  • 这个演讲最后本来有一个恶搞。虽然是录播,但我原本打算在最后假装有问答环节,问「大家有没有问题要问?」,然后停顿一分钟,再说「好,那没有问题就拜拜啦」。因为这样会让超时更加严重,而且会有点让人讨厌,所以最后就去掉了。现在的结尾的意思是「用 Python 之杖赐予各位听众 Pythonic 原力」。
  • PyCon China 网站也太难看了……

欢迎来 PyCon China 2020 听我的演讲(附深圳线下会场日程)

11/23 Update:演讲日程已发布,详情见 PyCon China 2020 官网报名页面

今年 PyCon China 2020 因为疫情原因改为 70% 线上+ 30% 线下(时间为 11 月 28 号和 29 号两天)。线上票免费,线下聚会票 9 块 9(北京/上海/深圳)。如果你刚好在深圳的话,那么好消息是——深圳仍然保留了线下会场,演讲会在线下进行(文末有深圳场的详细日程),但票价仍然不变。

欢迎访问 PyCon China 2020 活动主页报名和购票(目前活动页面还没有给演讲日程安排,嘉宾列表也并不完整)。尽管线上票免费,你想花钱的话也可以通过买捐赠票和 VIP 票来支持 PyCon China。

今年我提交了一个十分钟演讲,内容虽然和 Python 相关,但其实是堂英语课——《Python 技术名词发音指南》。时间在 11 月 29 号下午一点。简介如下:

Django 怎么读?十个 Python 程序员也许会有十种读法。如果这个你刚好会读的话,那么还有 Werkzeug、SQLAlchemy、Gunicorn、PyPI、Bokeh、Huey…… 根据可靠来源,因为错误的发音,两个 Python 程序员在交谈时平均每小时会浪费三分钟时间用来互相确认和纠正名词发音。为了减少整个 Python 社区因为错误的技术名词发音产生更多的熵,这个演讲带来了一个可信度 95% 的 Python 技术名词发音指南。

附 Pycon China 2020 深圳会场安排:

  • 时间:11/28 上午 9 点
  • 地址:深圳市南山区科技园南后海大道 2378 号芒果网总部大厦 10 楼(入场需展示「i深圳」绿码),靠近地铁 9 号线深大南站(328 米)或软件基地公交站(305 米)
  • 停车: 5 元/小时,35 元封顶
  • 会场负责人:代少飞
  • 报名链接:https://www.bagevent.com/event/6975077

议程安排如下:

(上午看线上主会场直播)

 

下午深圳线下会场议程安排

13:00-13:35

好好写代码:遗失的 Python 编程原则

赖勇浩-广东天勤科技有限公司

13:35-14:10

Nix for Pythoneer

黄毅-福里斯信息技术(深圳)有限公司工程师

14:10-14:45

利用 Python 在 Azure 上玩转机器学习

卢建晖-微软技术社区区域总监

14:45-14:20

Lightning — 现代的无代码 Admin 

Jeff Kit – 深圳杰明科技与闪电数据管理创始人 

14:20-14:30

中场休息&茶歇

14:30-15:05

从 Django 历史漏洞看安全编码规范的重要性

杨文涛 YangWentao-奇虎 360 渗透测试工程师

15:05-16:40

用 Python 从 0 构建一个简单的脚本语言

廖茂文-《生成对抗网络原理剖析与 TensorFlow 实践》作者

16:40-17:15

智能问答系统在西山居中的落地

黄鸿波-西山居人工智能领域专家

17:15-17:50

K12 教育里的 Python 和 AI 

刘敬韬 – 可可乐博科技 CTO

17:50-18:00

闪电演讲

少儿编程课程设计中常用的库与工具

董轶-爱派森课程研发&讲师

3 年 5 跳的遍历 Python 职业的踩坑之旅 

李红睿 – 富途证券工程师 

 

南京再见!

去年五月份来到南京,在这里待了一年半,前两天回家了。本来是打算来南京找工作,顺便体验下城市生活,但是因为各种事情一再搁置找工作的事情:做外包和咨询项目、组织和参加技术大会、写书。一年后发现和呆在家里没有什么区别,反倒不如先回家,把书写完再做打算。

在这里挣的钱大部分都留在了这里,唯有房子里的东西却一件不落的带了回去。去时只有一个面包车的行李,回来时一辆小皮卡差点没装下。不过这一车的零零碎碎倒是并没有花什么钱,大部分家什尤其是大件都是二手货。除了买电子产品,大部分还是被住和吃掉了。

在南京的一年多里,和好朋友阿宽只见了三面,一直想去拜访高中在豆瓣认识的老郭也始终没能成行,去年参加黑客松约着要去东大微软学生俱乐部做关于 Flask 的分享,现在也只好暂时泡汤。而对这个城市本身,也没来得及完全熟悉它。钟山风景区去了大概有七八次,但是还没有完全逛完所有景点。在钟山风景区里骑自行车是非常舒服的事情,比这还舒服的事情是骑车下坡。最喜欢的下坡路线是从灵谷寺路和水榭路汇合点开始,先沿着灵谷寺路下坡冲到灵谷寺景区门前,然后右拐一路经过非常长的一段下坡冲到体育学院。陵园路梧桐树更漂亮但是红绿灯和路口太多,如果你走这里下坡的话,那么建议再费点力气向东爬上卫岗的大坡,然后就可以体验到另一个更陡峭的大下坡。说起来在长江大桥骑车到了江北也会有一段大下坡,但是那一段就没有那么舒服了,因为没有几米是平坦的水泥地,一路下去准会被颠得骨头散架,手脚发痒。blank

顺便提一句,去了灵谷寺景区发现灵谷寺是最没意思的地方(看到崭新的门面和门楹上挂的标语就没有进去的兴趣了),而无量殿(国民革命军阵亡将士祭堂)、淞沪会战阵亡将士纪念碑和灵谷塔(国民革命军阵亡将士纪念塔)才是参观的重点。我在爬上灵谷塔这一天(2020 年 9 月 19 日)才知道蒋中正就是蒋介石,中正是名,介石是字,可怜我在台北参观中正纪念堂的时候还一直以为蒋中正是蒋介石的儿子或孙子……

因为我大部分时间都呆在房间里,在南京郊区还是在徐州农村其实没有太大区别。既然用不到什么市政资源,那么都是一样的用水,用电,用连不上 Google 的网。除了拿快递和看电影比较麻烦外,回到家好处很多,生活空间更宽裕了,平时也不用小心翼翼担心吵到楼下。住在南京养宠物太麻烦,只能去逗小区里居无定所的的猫狗。而在家里的话,我们家的狗以及邻居、邻居的邻居的狗都是招手即来(挥手也不走)。奶奶家不仅有猫有狗,还有兔子和鸡。在村外走远一点偶尔还会遇到被赶着散步的羊群和牛群。在农村也不会有那么多密集、丑陋和无聊的政府宣传画。很多美食好吃便宜不用排队,物价很低,在超市和街上买东西不用犹豫。

过一段时间也许会怀念在南京的生活。怀念钟山风景区的梧桐树和栖霞山的枫叶;怀念孝陵卫地铁站旁罗汉巷的梅干菜锅盔;怀念南京东郊那间出租房里舒服但是没藏着音乐城的红沙发。

南京再见!或许哪天再回去。

我和 PyCon China 的故事

从参加 PyCon China 2018,到组织 PyCon China 2019,再到现在 PyCon China 2020 正式启动,我想这是一个很好的契机来记录下这两年我和 PyCon China 的故事。顺便也让更多的人了解 PyCon China,并且可以参与进来,让它变得更好。

这是一个小小的 PyCon China 私人切片,因为我已经退出筹备组,所以不代表官方立场。写起回忆来难免会有一堆牢骚不吐不快,为了避免让这篇文章变成一份扎手的手札,棘手的手记,我调整了抱怨浓度,牢骚大概只占一半,不会让你心烦。另外也因为我的视角有限,所以一些观点和想法或许有失偏颇,仅供参考,同时欢迎撰文补充和扩展。

参加 PyCon China 2018

2018 年 9 月 16 号,某位读者建议我去报名 PyCon China 演讲,这是我第一次知道 PyCon China。我一边推脱说我做不来,一边在想自己如果去的话要讲些什么好,结果当晚就报上了名,第二天又在组委会的「怂恿」下把演讲类型从快速演讲(10 分钟)换成了主题演讲。26 天后去北京参加了 PyCon China 2018,这是我第一次参加技术大会,回来后写了这篇总结文章,一半篇幅都是牢骚。

组织 PyCon China 2019

2019 年 4 月 20 号,在准备提交 2019 年的 PyCon China 演讲申请的时候,基于 PyCon China 2018 的体验向大会负责人辛庆老师提了一些改进建议,比如没有闪电演讲,演讲视频没有上传到网络上公开……然后聊着聊着就加入了筹备组。

2019 年从四月底到十月底这半年时间,我基本完整参与了 PyCon China 2019 的筹备。在整个筹备过程里,大部分事情都掺和了一些:协调规划各类事情、策划和实施闪电演讲、更新大会网站、翻译大会资料、审核宣传文章、创建和更新 Twitter 和 Facebook 账号、接待讲者、做主持人、整理上传幻灯片、写和改进各种表单/界面/措辞……在这段时间里,除了做各种杂务,我还完成了三个演讲:

这三个演讲花了很多时间准备,虽然完成的都不完美,但从收集到的会后反馈来看有很多人喜欢,这让我很开心。

在筹备工作里,我最主要的工作成果是策划闪电演讲,以及推动两个改进计划:「品牌形象建设计划」和「工作人员 Credit 计划」。

闪电演讲

4 月和辛庆老师沟通的时候,我提议增加闪电演讲专场来让大会变活泼一点,并且自荐可以负责策划和实施落地。然后我就开始负责做这件事情,一直到九月结束,跟踪了整个流程:写策划案、写宣传文章、发布相关社交动态、和讲师沟通、组织试讲、做主持人、自己出闪电演讲、提前跟摄影师要录像、剪辑录像、上传幻灯片和录像。

闪电演讲的模式大致参考了 PyCon USPyCon AU。做这件事的出发点是让 PyCon China 现场气氛不要那么严肃,稍稍有趣一点,而且作为大会的最后一个环节,可以让大家有一个热烈欢快的结尾。本来的设计是闪电演讲作为整个大会独占的最终环节,放到主会场。但事情的发展慢慢就超出了控制。一开始是因为酒店预算有限,主会场上午用完下午要拆成两个会场,所以原定的闪电演讲主会场就被换到了 Web 分会场结尾。接着因为酒店交还场地的时间限制加上上午开始时间延后导致闪电演讲独占场次变成和其他几个会场一同进行。尽管如此,我还是继续把它作为大会结尾来推行,保留了 PyCon China 2019 结束语部分(本想再加一个工作人员 Credit 环节,但是因为各种原因作罢了)。

最终效果和预想打了一些折扣,这个折扣是很多事情共同造成的。首先是大家都不熟练,我第一次做主持人,讲者们(包括我)第一次做严格限时 5 分钟的闪电演讲,许多听众大概也是第一次看闪电演讲。再加上大多演讲都比较严肃,讲者或许更关注专业性而不是有趣好玩,听众似乎也更期待和习惯前者。尽管前一晚和几位讲者试讲时一再强调 5 分钟限时,但是最终严格限时策略也受阻,Noah Chen 赖着不走我却没办法,打断严肃的演讲又看起来没礼貌。算起账来,场地也要背一口锅,讲台太高,观众离讲台太远,观众分隔距离太大,导致讲者和听众、听众和听众之间很难建立连接。从我几次演讲的体验来看,大学才是更好的技术大会场地,酒店还是更适合严肃无聊的企业会议。

最后一共收到 20 个闪电演讲申请,上海场留下 7 个:

  • Connect to the World of Python’s Community – Noah Chen
  • 500 行 Python 写一个渲染器 – 谭啸
  • 基于 OwlReady2 的人机交互 – 宋从威
  • Byte Code 的革命 – 赵俊德
  • 使用 Sphinx 制作 Web 文档 – 陈照强
  • 一键将 C/C++ 代码转换为 Python 能调用的代码 – 韦泽华
  • Python 虚拟环境和依赖管理工具大乱斗 – 李辉

我在闪电演讲开场说这是 PyCon China 第一个闪电演讲专场,但也有可能是最后一个。看了今年的宣传文章,啊,乌鸦嘴!演讲时间又变回了无聊的十分钟快速演讲模式,很显然我的设想「闪电演讲开放给现场听众,随便报名,投票决定要听哪个」也没成。我在想,假如闪电演讲继续像去年那样(乱搞),也许就可以通过活泼的演讲形式和外界宣传来吸引到气质相符的演讲和演讲者,进一步影响到参会者,最终让整个大会轻松活泼一点。

blank

如果你对这个不甚完美的最后一次 PyCon China「真 · 闪电演讲」感兴趣的话,可以移步 bilibiliYoutube 看现场录像。

品牌形象建设计划

在我加入之后,发现 PyCon China 的品牌形象很混乱:微博账号长久停止更新,也没有建立 Twitter 和 Facebook 账号,各个账号的名称和头像都不统一,所以我就想建设一个统一完整的品牌形象,让它专业一点。

先是推动确立了统一的官方 logo,当时确定的主 logo 据说是一位新加坡华人工程师设计的作品(具体姓名忘记了,看官若了解还请评论告知),我很喜欢这个 logo:

blank后续的大会 logo 基本都是这个图案的变种。对于文字版的 logo,我选了基于这个 logo 设计的 PyCon China 2012 版的带文字 logo(作者是陆卫锋):

blank

然后用我最熟练的平面设计软件——Windows 画图删掉了「PyCon 2012」,调整了「CHINA」和图形的距离,让文字里东方明珠塔的塔尖收进图案内部,最终确立了文字版本的主 logo:

blank

大部分社交账号都统一使用这个 logo,官网上因为需要横向 logo,所以用了一个主 logo 横版的变体(由错姐设计)。只剩下两处没统一:新浪微博当时似乎是限制修改头像没改成,而 GitHub 组织大概是被漏掉了。统一了 logo 之后,接着结束了 PyCon China 没有 TwitterFacebook 账号的历史,并且推动恢复了新浪微博账号的运营。但是拜托今年不要放弃更新 Twitter 和 Facebook 呀!29 号在公众号和新浪微博发布动态,而 Twitter 和 Facebook 一直都静悄悄……直到五天后才在 Twitter 上同步消息,而 Facebook 依然没有更新。这时演讲征集截止时间只剩下两天(29 号开放 7 号就结束似乎有些太短了?)

顺便说一句,发现今年出现了一个新的 PyChina logo,建议配套使用:如果社交帐号名称是 PyCon China,那么就用 PyCon China 的 logo;如果名称是 PyChina,那么就用 PyChina 的 logo。bilibili 账号这样搭配就显得混乱,违背了统一品牌形象的初衷(今年或许可以继续完成统一 logo 这件事)。

工作人员 Credit 计划

在 2018 年的大会网站以及我加入时 2019 年的大会网站上只列了一个出品人列表,这个列表更像是一个曾经参与、短暂参与和偶尔参与的混合列表。列表里有的人什么都做,比如李者璈(Manjusaka),和辛庆老师一样做各种协调和杂活,同时还全力支持多个城市的会务,兼职主持人(可塑性极强,如果讲者缺席了就可以当晚熬夜准备演讲第二天填空缺);有的人什么也不做,比如往届参与的成员和初创成员;更多的则是参与演讲议题评审的审稿人;而有的人做了很多工作,却没有被列到出品人列表里面,比如大部分真正处理各类具体筹备工作的工作人员其实大都不在这个列表里:

  • 辛庆(辛姐/Cynthia Xin):协调各类事情,做了很多杂务,因为太多所以不知道列出哪件比较好,反正至少顶了 PyCon China 半边天。
  • 许银(许老板/Jamie Xu):协调现场会务,处理现场各种「火灾水灾」事件。
  • 晁倩(倩姨/啊Q/Queena Chao):PyChina 公众号主理人和现场志愿者负责人,2019 年大部分公众号文章的编辑,同时也帮忙做各种杂务。
  • 王梦兰(大猫/Alan Wang):身兼多职,主营财务、社交媒体维护和资料翻译。
  • 王晓珂(Nicoco Wang):负责物料采购和现场签到,除了买买买,也一起帮忙做翻译和文章审阅。
  • 吴妙璇(错姐/小错/Mxuan):大部分现场物料、文章配图、幻灯片主题、T 恤等纪念品的设计都出自错姐之手。更多她的设计可以在《PyConChina大会设计2017-2019》看到。

在自己负责的领域之外,这里的每个人都会帮忙做各种杂务,也在现场做各种会务支持。除了我比较熟悉的这些人,还有很多其他工作人员和志愿者也一样在背后默默支持。尽管如此,大部分听众却没法了解到这些。

让积极做事情的人感到被认可是一件很重要的事情,电影结尾的演职员表即便滚动的再快,也会有人仔细在里面找自己的名字。一个合理的 Credit 机制可以让工作的时候有明确的分工和职责,有了认可也会让大家更有动力参与和投入。基于这些考虑,所以有了这个「工作人员 Credit 计划」。这个计划的重点就是制作一个分类的工作人员列表放到官网上,也推动了为公众号文章署名之类的小改进。

经过大家的共同努力,下面就是最终整理出的工作人员列表(篇幅所限,出品人没有列出,完整的工作人员列表可以在大会网站的工作人员页面看到):blank

这个页面其实还是未完成的状态。有一些特殊贡献没能列出,比如刘玉龙和何翔宇上传了所有的演讲视频到 bilibili 和 YouTube(中间李者璈也有协助);王梦兰在做新浪微博账号的更新和维护,中间还帮我接管过一段时间的 Twitter 和 Facebook。现场志愿者列表始终没有整理出来,估计也永远不会整理完了。

2019 未完成

筹备过程漫长,许多事情等不到答复可以自己动手,实在等不到那就放弃或折中,而更多的问题是在现场会务上集中出现。因为有太多变数和不确定性,而且又没有足够的时间进行预演,再加上缺乏流程约定和验收,上海场遇到了太多问题,仅我自己见到和体验到的就有:连接线不够长,电脑没法放到讲台;现场网络很差;有道同传失灵;主会场屏幕幻灯片两边有一些看不到;翻页笔频繁失灵;翻页笔不支持左右翻页;(继 2018 再一次)幻灯片尺寸错误;两个会场临时隔断墙隔音不好,分会场变大杂烩场;摄影师拍照片手法单调,拍讲师看不到背景幻灯片,不抓拍开场等关键画面;摄影师录像漏拍,而且没有备用摄像,导致上午主会场 laike9m 的演讲录像不完整……问题远远不止这些,细说起来大概三天三夜说不完。关于上海场会务的诸多问题在郑天航写的《在 PyCon Shanghai 2019 中我该做什么》里有很详细的介绍。

和大多数人类合作活动一样,PyCon China 2019 包含了各种妥协、事故和拖延,但是总有各种将就、补救和掩盖来让一切看起来还不错。对于观众来说看起来还不错就够了,但是对于组织者来说这远远不够,因为我们本可以做得更好,至少也要凭着这一次的经验,让下一次更好一点。

因为在筹备组这半年一直没工作,所以做什么事情都会慢慢变成全职,偶尔几天全职做外包,偶尔几天全职写书,偶尔变成全职参与 PyCon China(也因此被调侃家里有矿,所以筹备组里的人都叫我小李老板)。同样也是因为没工作,大多时间都花在了 PyCon China 筹备和准备演讲上,这半年除了拿过一次稿费做了一个外包项目外没有任何收入,生活也变得很混乱。为了让生活重回正轨,所以决定不再参与 2020 大会的策划。2019 年 10 月 27 号,成都场结束,PyCon China 2019 组织工作进入尾声,我就在筹备组群聊和大家说了再见。

在 PyCon China 的这段时间里,认识了很多有趣的朋友,收获了很多快乐的时光,希望大家一切顺利!

期待 PyCon China 2020

2020 年 10 月 29 号,PyCon China 2020 宣布正式启动了,会议时间定在 11 月的 28~29 号。希望今年的 PyCon China 可以办得更好……

等等,官网哪里去了?不知道为什么,PyCon China 2020 已经开始筹备了,官网却处于下线的状态(不论是 pyconchina.org.cn 还是 cn.pycon.org)。我认为官网是技术大会或者说任何现代品牌的最主要的网络门面,即使用户更喜欢关注微信公众号和新浪微博。对于一个技术大会来说,在网络上搜索关键词找不到官网,找到官网却打不开,这都会向观众传达一个很不专业的信号。修复并更新官网应该是在公开 PyCon China 2020 消息之前就应该做好的事情,也是现在的当务之急(上文提到官网时使用的链接指向的是我临时放到 greyli.com/archives/pyconchina 下的网站存档,基于 2019 大会网站源码生成)。

虽然不太可能实现,但我还是想说,我们应该有个博客……去年因为种种原因没能做成这件事,最终只是写了一篇文章放到 PyChina 的博客上,但我还是建议把 PyCon China 博客独立出来。有一个博客可以更方便发布各种消息给搜索引擎和超链接爱好者,而不仅仅是放到微信局域网里的公众号上服务微信用户。

尽管不再参与组织今年的 PyCon China,但是翻开去年写的《PyCon China 2020 规划》,还是有一些改进建议想一并写在这里,也许能够提供一些启发促成一些改进(不感兴趣请猛按空格键)。

首先是议题收集和审核流程需要改进。我在参与组织 FlaskCon 2020 时主办方使用的议题管理工具 Sessionize 很好用(议题收集、评审和日程表导出一体化工具),投票方式更简单合理,每个评审员独立完成投票,通过多重对比得出每个演讲的权重顺序,最终综合所有评审员的投票得出结果。这样远比微信群讨论和看板事项留言更高效,也比赞同反对式的投票方式更科学。而且可以调用其 API 生成网站议程时间表,不用投入大量精力手动同步议程变动。今年会议改线上刚好可以用 Sessionize 的免费社区版,但是考虑到今年演讲申请大都已经投递了,估计没法用(除非手动导入议题数据……),但是明年或许可以考虑。接着去年的想法,如果可能的话,取消工作人员页面的出品人概念,改为工作人员的子分类——议题审核组,只保留列表中该年参与的成员。

在议题申请上去年还有一个改进想法是在议题申请表里添加更多细化选项,比如「演讲难度」。标注演讲难度可以让听众有一个心理期待值预判,也可以让讲者不会有太多心理负担(担心话题太简单被人骂,担心话题太深奥也被人骂)。

另外,协作工具也需要改进。去年就尝试把全部工作沟通从微信转移到 Slack,但是过程并不顺利,不知道今年是不是又回到了微信群聊。我认为不应该互相迁就,而是要想办法让所有人都能正常上网,然后使用最好用的工具。

我在 19 年最后,花了很长时间来做的事情就是整理出一份《PyCon China 文档和资源导航》,收集了所有相关的资源和文档信息,以便能更好的利用这些文档,方便找到自己需要的资源和链接。同时也尽可能的扩充了大部分事项的检查清单,每一个事项都需要一个用来执行和验收的检查清单,这一点在会后总结时已经达成共识。同时我们也需要写更多的文档来规约和优化会议细节。比如分别交给讲者、听众、筹备组新成员、现场志愿者的入门指引和注意事项。​这些东西再统统加入文档和资源导航以便于查找,并且逐年迭代和优化。

最后,PyCon China 2020 需要新鲜的血液、肺泡和脑细胞,欢迎加入志愿者团队和筹备组,欢迎报名演讲

有钱的出钱,有力的出力,柴禾多了,火就旺了。祝 PyCon China 越来越好。


延伸阅读: