PyConCN-2019-会议笔记-全-
PyConCN 2019 会议笔记(全)
001:1. 基于 PyQt + QScintilla 的 IDE 对 RobotFramework 的 DSL 进行编程 🛠️

概述
在本节课中,我们将学习如何使用 Python 的 PyQt 和 QScintilla 库,为 RobotFramework 的领域特定语言(DSL)构建一个自定义的代码编辑器。我们将探讨 RobotFramework 的基本概念、现有工具的局限性,并逐步讲解如何实现语法高亮、代码补全等现代 IDE 功能。
背景知识介绍
在开始构建编辑器之前,我们需要了解几个核心概念。
领域特定语言(DSL)
DSL 是 Domain Specific Language 的缩写。与 C++、Java、Python 等通用编程语言不同,DSL 是为解决特定领域问题而设计的语言。例如,在数学建模领域,可能只需要运行模型,而不需要复杂的编程语法。
RobotFramework
RobotFramework 是一个基于 Python 编写的自动化测试框架。它采用关键字驱动(Keyword Driven)模式,主要用于验收测试驱动开发(ATDD)。该框架通过插件支持 Web 端、移动端等多平台测试。
Qt 与 PyQt
Qt 是一个优秀的跨平台 C++ 应用程序框架,支持桌面、服务器和移动端开发。例如,Linux 的 KDE 桌面环境就是基于 Qt 开发的。PyQt 是 Qt 的 Python 绑定,常用于开发图形界面应用和数据可视化工具。著名的 Python IDE Eric 就是基于 PyQt 开发的。
QScintilla
QScintilla 是 Scintilla 编辑器组件在 Qt 上的实现。Scintilla 是一个开源的源代码编辑组件。大家熟知的文本编辑器 Notepad++ 就使用了 Scintilla。
PyParsing
PyParsing 是一个轻量级的、纯 Python 编写的语法解析器。在构建 DSL 编辑器时,我们需要进行词法分析和语法分析。与传统的 Lex/Yacc 等重型工具相比,PyParsing 更加简单易用。
RobotFramework 快速介绍
上一节我们介绍了相关背景知识,本节中我们来看看 RobotFramework 的具体情况。
RobotFramework 由诺基亚在 2008 年发起,是一个开源的自动化测试框架。其最新稳定版本是 3.1.2(通常缩写为 RF)。它采用 ATDD 模式,强调在开发过程中同步编写验收测试用例,以持续验证功能。
以下是官方对 RobotFramework 架构的说明:
- 底层:被测系统(System Under Test)。
- 中间层:测试工具(Test Tools)。
- 上层:
- 测试库(Test Library):由开发工程师编写,包含具体的测试逻辑。
- RobotFramework 核心:负责将测试数据与测试库关联并执行。
这样讲可能有些抽象,接下来我们结合一个例子来理解。
一个简单的例子:SSH 客户端
假设我们有一个用 Python 3.5+ 编写的 SSH 客户端测试库。它使用了 type hint 特性来增强代码可读性。
# 文件名: ssh_client.py
# 这是一个 Test Library
import paramiko
class SSHClient:
def __init__(self, host: str, port: int, username: str, password: str):
self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.client.connect(host, port, username, password)
def execute_command(self, command: str) -> str:
stdin, stdout, stderr = self.client.exec_command(command)
return stdout.read().decode()
接下来是 RobotFramework 的测试脚本(Test Data),由 QA 工程师编写。
# 文件名: test_ssh.robot
*** Settings ***
Library ssh_client.py WITH NAME ssh
*** Variables ***
${HOST} 192.168.1.100
${PORT} 22
${USER} admin
${PASS} secret
*** Test Cases ***
Check Disk Space
${output}= ssh.Execute Command df -h
Log ${output}
在这个脚本中:
*** Settings ***部分用于配置,例如加载测试库。*** Variables ***部分定义变量。*** Test Cases ***部分编写具体的测试用例。
运行这个测试用例非常简单,使用以下命令:
robot --outputdir reports --pythonpath . test_ssh.robot
如果一切顺利,你会得到一个绿色的测试报告,表明用例通过。点击报告可以查看详细的执行日志和变量值。
遇到的问题与改进思路
RobotFramework 虽然改进了开发测试流程,但也存在一些可以改进的空间。
以下是使用 RobotFramework 时可能遇到的问题:
- 制表符与空格混淆:RobotFramework 基于表格进行语义分割,混合使用制表符(Tab)和空格可能导致用例运行失败。
- 全角/半角字符问题:不小心输入全角字符(如全角空格)也会导致问题。
- 资源文件路径问题:测试脚本中引用的资源文件路径可能是绝对路径或临时路径,当环境迁移时(如从开发机到生产环境),用例可能因找不到文件而失败。
- 现有工具体验:虽然有专门的编辑器(如 RIDE),但其界面和用户体验仍有提升空间。
因此,我们决定构建一个更酷、更强大的自定义编辑器来改善这些问题。
构建自定义 DSL 编辑器
上一节我们分析了现有工具的不足,本节中我们来看看如何利用 PyQt 和 QScintilla 构建一个更好的编辑器。
我们称这个工具为 RDT(Robot Development Tool)。其架构如下:
- 底层:PyQt5, Python, QScintilla。
- 核心功能层:
- 词法/语法分析(Lexer/Parser)
- 抽象语法树(AST)
- 代码补全(Auto-completion)
- 语法标记(Token)
- 上层:用户界面(UI)系统。
在 QScintilla 中,任何在语法中有意义的元素(如注释、关键字、变量名、函数名)都可以定义为一个 Token。QScintilla 原生支持 40 多种语言的语法高亮,但不包括 RobotFramework。幸运的是,它提供了接口供用户自定义语法。
使用 PyParsing 进行语法分析
对于语法解析,我们使用轻量级的 PyParsing 库。它无需编写复杂的 BNF 范式,可以快速高效地实现词法解析。
例如,解析一个简单的 “Hello; World!” 规则:
from pyparsing import Word, alphas, Literal
# 定义规则:一个单词 + 分号 + 另一个单词 + 感叹号
word = Word(alphas)
parser = word + Literal(';') + word + Literal('!')
# 解析字符串
result = parser.parseString("Hello; World!")
print(result) # 输出: ['Hello', ';', 'World', '!']
对于 RobotFramework,我们可以用类似的规则定义其语法,提取出单词、值等基本元素。
实现编辑器功能
接下来,我们讲解如何实现编辑器的核心功能。
1. 定义 Token 与语法高亮
我们需要继承 QScintilla 的相关类来实现自定义的词法分析和高亮规则。
from PyQt5.Qsci import QsciLexerCustom
class RobotFrameworkLexer(QsciLexerCustom):
def __init__(self, parent=None):
super().__init__(parent)
# 定义不同 Token 的样式(颜色、字体等)
self.setColor(QColor("blue"), 0) # 样式0:蓝色,用于关键字
self.setColor(QColor("green"), 1) # 样式1:绿色,用于注释
def language(self):
return "RobotFramework"
def styleText(self, start, end):
# 核心方法:框架调用此方法来请求对指定文本范围进行样式设置
# 在这里,我们使用 PyParsing 分析文本,并为不同部分分配样式编号
editor_text = self.editor().text()[start:end]
# ... 使用 PyParsing 分析 editor_text ...
# ... 根据分析结果,调用 self.setStyling(length, style_id) 设置样式 ...
关键方法说明:
styleText(start, end): 当编辑器内容需要更新时,框架会调用此方法,并告知需要渲染的文本起始 (start) 和结束 (end) 位置。我们需要在此方法内实现词法分析并为文本分配样式。
2. 实现代码补全
代码补全分为有上下文和无上下文两种。
- 无上下文补全:例如通用的关键字列表。我们可以通过 QScintilla 的 API 直接提供这些单词列表。
- 有上下文补全:例如某个对象的方法列表。这需要更复杂的 AST 分析来获取上下文信息。
3. 设置编辑器属性
我们可以通过 QScintilla 的设置命令来配置编辑器的其他特性,如编码、缩进、括号匹配高亮、代码折叠等。
editor = QsciScintilla()
# 设置使用 UTF-8 编码
editor.setUtf8(True)
# 设置使用制表符缩进
editor.setIndentationsUseTabs(True)
# 启用括号匹配高亮
editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)
性能优化:解决 UI 阻塞问题
在实现过程中,我们遇到了一个关键问题:UI 线程阻塞。
当处理大型 RobotFramework 脚本(如 7000-8000 行)时,在主线程(UI 线程)进行实时的词法/语法分析会导致界面卡顿、滚动不流畅。
解决方案:将耗时的词法/语法分析工作放到后台线程执行。
- 当
styleText方法被调用时,我们不立即进行分析,而是记录需要分析的文本范围。 - 将这个分析任务提交到一个后台线程(Worker Thread)中执行。
- 后台线程完成分析后,通过 PyQt 的 信号与槽(Signal & Slot) 机制,将分析结果(样式信息)发送回主线程。
- 主线程(UI 线程)接收到结果后,再进行实际的界面渲染。
这种方式确保了 UI 线程始终保持响应,即使分析工作尚未完成,用户也可以流畅地滚动编辑区,分析结果会稍后“追赶”上来并更新高亮显示。这类似于在 Word 中拖动一个大型文档,内容会逐步加载渲染的效果。
总结与展望
本节课中我们一起学习了以下内容:
- RobotFramework 介绍:我们了解了 RobotFramework 的语法、用途及其在 ATDD 中的应用,并分析了现有编辑工具的不足,这构成了我们“造轮子”的动机。
- 自定义编辑器构建:我们深入探讨了如何使用 QScintilla 的 API 来定义自定义的语法标记(Token)、实现代码补全和语法高亮功能。
- 架构与性能优化:我们介绍了如何利用 PyQt 的信号与槽机制,将 UI 渲染与后台分析解耦,从而解决处理大文件时的界面卡顿问题。
本项目仍在持续开发中,未来计划进行更全面的测试,并与持续集成/持续部署(CI/CD)工作流进行更深度的集成。
联系方式
欢迎通过微信与我进一步交流讨论。


002:舆情监控系统开发全流程 🐍

在本节课中,我们将学习如何利用Python构建一个实用的金融舆情监控系统。我们将从核心概念入手,逐步拆解系统的各个模块,并展示关键代码的实现方式,让初学者也能理解其工作原理。
公司及个人背景介绍
上一节我们概述了课程内容,本节中我们先了解一下项目背景。华能信托是华能集团旗下的金融子公司,主要从事信托业务,核心是进行资金的整合与匹配。演讲者王宇涛并非科班出身的程序员,而是在工作中自学Python,并创立了公司的金融科技小组。
将IT技术与金融、教育、医疗等行业结合,能产生巨大的“比较优势”。在这些领域,掌握基础的编程技能就能解决许多实际问题,创造显著价值。
舆情监控系统的核心价值 💡
在金融行业,核心工作可概括为“找钱”和“找项目”。投资一个项目(如阿里巴巴、万科)前,必须持续关注该公司是否出现负面舆情。手动监控上百家公司的工作量极大,因此自动化舆情监控系统显得至关重要。
对于一个百亿规模的项目,一天的潜在损失可能高达数百万。因此,及时的风险预警对金融公司管理数千亿资产而言,是必不可少的环节。
系统架构与核心模块
以下是舆情监控系统的主要构成模块,我们将逐一进行解析:
- 信息爬取:从互联网抓取目标公司的相关新闻。
- 数据清洗:对爬取的原始信息进行整理和优化。
- 持续监控:实现24小时不间断的自动化爬取。
- 舆情评分:对新闻进行正面或负面判断,并量化评分。
- 风险预警:当评分低于阈值时,自动触发预警机制。
- 反爬策略:应对目标网站的反爬虫措施。
模块一:基础网页爬取
爬虫的第一步是获取网页源代码。对于大多数网站,使用Python的requests库可以轻松实现。
获取Python官网首页的示例代码:
import requests
url = ‘https://www.python.org‘
response = requests.get(url)
print(response.text)
爬取百度新闻的示例代码(添加请求头模拟浏览器):
import requests
url = ‘https://news.baidu.com‘
headers = {‘User-Agent‘: ‘Mozilla/5.0‘}
response = requests.get(url, headers=headers)
print(response.text)
通过以上代码,即可获得网页的HTML源代码,为后续的信息提取打下基础。
模块二:信息提取与数据清洗
获取网页源码后,需要从中提取出有用的信息,如新闻标题、链接、来源和日期。正则表达式是完成这项任务的通用且强大的工具。
使用正则表达式提取新闻信息的示例代码:
import re
# 假设 html_text 是爬取到的百度新闻页面源代码
pattern = r‘<a href=“(.*?)“.∗?>(.∗?)</a>‘
results = re.findall(pattern, html_text, re.S)
for link, title in results:
print(f“标题: {title.strip()}, 链接: {link}“)
提取后的文本可能包含多余空格或无效字符,需要进行清洗。
数据清洗的示例代码:
def clean_text(text):
# 去除首尾空格及换行符
text = text.strip()
text = text.replace(‘\n‘, ‘‘).replace(‘\r‘, ‘‘)
# 更多清洗规则...
return text
模块三:实现24小时持续监控
实现不间断爬取的核心是让程序持续运行。最简单的方法是使用while循环。
使用while循环实现持续爬取的示例代码:
import time
while True:
# 执行爬取任务
crawl_news()
# 休息一段时间,例如1小时
time.sleep(3600)
为了更智能地调度任务,也可以使用schedule库来定时执行。
模块四:舆情评分系统
舆情评分是系统的核心。一个简单有效的方法是构建一个负面关键词库,检查新闻标题或正文中是否包含这些词。
基于负面关键词列表的评分示例代码:
negative_words = [‘违约‘, ‘诉讼‘, ‘亏损‘, ‘下跌‘, ‘造假‘]
def score_news(title, content):
score = 100 # 初始分数
for word in negative_words:
if word in title:
score -= 5 # 标题中出现负面词,扣分
if word in content:
score -= 3 # 正文中出现负面词,扣分
return max(score, 0) # 确保分数不为负
更高级的方法可以引入机器学习模型进行语义分析,但对于许多实际场景,关键词匹配方法已经足够有效且高效。
模块五:自动预警邮件发送
当监控到负面舆情(如评分低于80)时,系统需要自动发送预警邮件。Python的smtplib库让这个过程变得非常简单。
使用smtplib发送邮件的示例代码:
import smtplib
from email.mime.text import MIMEText
def send_alert_email(subject, content, to_addr):
from_addr = ‘your_email@163.com‘
password = ‘your_authorization_code‘ # 注意是授权码,非登录密码
msg = MIMEText(content, ‘plain‘, ‘utf-8‘)
msg[‘From‘] = from_addr
msg[‘To‘] = to_addr
msg[‘Subject‘] = subject
try:
server = smtplib.SMTP_SSL(‘smtp.163.com‘, 465)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
print(‘邮件发送成功‘)
except Exception as e:
print(f‘邮件发送失败: {e}‘)
模块六:应对反爬虫策略
部分网站(如微信搜狗)设有反爬机制。常见的应对方法包括使用IP代理和模拟浏览器。
使用IP代理的示例代码:
import requests
proxy = {‘http‘: ‘http://12.34.56.78:8080‘, ‘https‘: ‘https://12.34.56.78:8080‘}
url = ‘https://weixin.sogou.com‘
try:
response = requests.get(url, proxies=proxy, timeout=5)
print(response.text)
except:
print(‘代理请求失败‘)
对于动态渲染的网页(大量使用JavaScript),requests库无法直接获取内容,此时可以使用selenium库模拟真实浏览器操作。
使用selenium的示例思路:
from selenium import webdriver
# 启动浏览器,手动登录一次后,可维持登录状态进行爬取
driver = webdriver.Chrome()
driver.get(‘需要登录的网站‘)
# … 人工完成登录操作 …
# 此后即可通过driver.page_source获取渲染后的页面源码
拓展应用:Python在金融科技的其他场景
除了舆情监控,Python在金融科技领域还有广泛的应用前景:
- 面试宝:与人力资源结合,开发微信小程序进行远程视频面试。
- 资金雷达:利用爬虫和RPA技术,寻找资金需求方与供给方。
- 大数据风控:应用逻辑回归、决策树等机器学习模型进行信用风险评估。
- RPA流程自动化:自动化重复的办公流程,如数据对账、报表生成。
- 智能问答机器人:通过预设问答对或自然语言处理技术,提供自动客服。
总结与展望
本节课中我们一起学习了如何用Python构建一个完整的金融舆情监控系统。我们从爬取、清洗、评分到预警,逐步拆解了每个模块的实现原理和关键代码。这套系统体现了Python在解决特定行业痛点时的强大能力与高效率。
技术的价值在于应用。将编程技能与金融、人力等领域的知识结合,能发现巨大的市场机会,创造“降维打击”式的竞争优势。未来,我们还可以将此系统产品化(SaaS服务),或探索智能投顾、量化交易等更多金融科技方向。

所有示例代码已分享在Mo平台(
https://momodel.cn)的“华小智Python金融”项目中,可供参考与实践。
003:用Python实现文本信息的结构化信息提取 📝

概述
在本节课中,我们将学习如何使用Python从非结构化的文本中提取结构化的信息。我们将从一个具体的业务场景出发,了解结构化信息提取的概念、方法、实现步骤,并最终探讨如何将模型部署为服务,实现持续集成与交付。
什么是结构化信息提取? 🤔
结构化信息提取是指从非结构化的文本数据中,抽取出结构化的、机器可读的数据的过程。
例如,一段关于“千岛湖”的介绍文本,经过提取后,可以得到诸如“名称:千岛湖”、“位置:浙江省杭州市”、“面积:573平方公里”等结构化的键值对信息。
对于人类而言,理解一段文本是容易的。但对于机器,它更擅长处理数据库表那样具有明确字段和类型的数据。因此,我们需要将人类的自然语言“翻译”成机器的语言,以便进行自动化处理和信息挖掘。
为什么要进行结构化信息提取? 🎯
结构化信息提取是许多智能应用的基础,主要用途包括:
- 构建领域知识库:抽取特定领域的实体和关系,形成结构化的知识网络。
- 辅助商业决策:例如,从招标公告中自动提取项目编号、预算、联系方式等关键信息,帮助业务人员快速筛选和跟进。
- 赋能智能服务:为信息检索、问答系统、情感分析等上层应用提供高质量的结构化数据输入。
如何实现结构化信息提取? 🛠️
实现文本的结构化信息提取,主要有两大类技术方案:传统方法和深度学习方法。
传统方法
-
基于规则的方法:
- 依赖领域专家构建词典和正则表达式规则。
- 优点:在规则明确、领域固定的场景下准确率高。
- 缺点:成本高、迁移性差、难以维护。
-
基于机器学习的方法:
- 无监督学习(如聚类):无需标注数据,但准确率通常不高。
- 有监督学习:需要标注数据。其中,条件随机场(Conditional Random Fields, CRF) 是处理序列标注(如命名实体识别)任务非常有效且经典的方法,在许多实践中表现优异。
深度学习方法
利用深度神经网络(如RNN, LSTM, BERT)自动学习文本特征,并进行端到端的预测。通常的流程是:文本 -> 词向量表示 -> 神经网络特征抽取 -> 输出预测标签。
项目实践建议:对于多数初学者或数据量不大的项目,推荐优先尝试CRF模型。它训练速度快,对序列数据建模能力强,通常能取得与复杂深度学习模型相近的效果,是性价比很高的选择。在拥有充足数据后,可以尝试结合深度学习模型(如BiLSTM+CRF)以追求性能的进一步提升。
为什么选择Python? 🐍
Python是实现文本信息提取任务的绝佳选择,原因如下:
- 丰富的生态系统:拥有大量成熟的数据处理(如Pandas)、可视化(如Matplotlib)和机器学习库(如scikit-learn, TensorFlow, PyTorch),方便快速验证想法。
- 强大的交互式工具:Jupyter Notebook等工具便于对文本数据进行逐步分析和探索。
- 简洁优雅的语法:降低了开发门槛,使开发者能更专注于算法和逻辑本身。
结构化信息提取实战步骤 🚀
整个流程可以概括为三个核心阶段:数据预处理、模型训练、服务化部署。其中,数据质量是决定模型效果上限的关键,务必重视前期的数据清洗和标注工作。
第一步:数据获取与预处理
数据通常来源于业务部门提供或通过网络爬虫获取。原始文本质量参差不齐,包含大量噪音。
文本清洗的目标是得到干净、连贯的句子。常见的清洗操作包括:
- 去除无意义的乱码和特殊字符。
- 修正错误的换行和空格(例如,将不应断开的词语连接起来)。
- 统一字符格式(如全角转半角)。
以下是一个英文文本清洗的示例流程,可供参考:
# 示例:简单的英文文本清洗步骤(中文场景需调整)
text = "Python is a Popular programming language. It's easy to learn."
# 1. 转换为小写
text = text.lower()
# 2. 纠正拼写 (此处为示意,实际需用库如pyspellchecker)
# text = correct_spelling(text)
# 3. 词干提取 (如将“programming”变为“program”)
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = text.split()
stemmed_words = [stemmer.stem(word) for word in words]
# 4. 去除停用词 (如 “is”, “a”, “the”)
from nltk.corpus import stopwords
filtered_words = [word for word in stemmed_words if word not in stopwords.words('english')]
cleaned_text = ' '.join(filtered_words)
print(cleaned_text) # 输出类似:python popular program language . easy learn .
中文分词是中文NLP的基础步骤。推荐使用jieba等工具,但需要注意,通用分词工具可能对领域专有词(如“千岛湖”)分割不准。此时,需要构建自定义词典来提升分词准确性。
样本增强:当标注数据不足时,可以通过规则对现有样本进行变换以扩充数据。例如,针对“联系人:张三”这类模式,可以将其中的“联系人”替换为“采购人”、“单位联系人”等同义词,与不同的姓名进行组合,从而生成新的训练样本。
第二步:数据标注与模型训练
数据标注:我们需要为文本中的每个词或字打上标签。常用的标注体系是BIOES:
B-{Tag}:表示一个实体的开始。I-{Tag}:表示一个实体的中间部分。E-{Tag}:表示一个实体的结束。S-{Tag}:表示一个单独的实体。O:表示非实体部分。
例如,“新安江水库”可以被标注为 B-LOC, I-LOC, E-LOC。
文本表示:计算机无法直接理解文字,需要将文本转化为数值向量。常见方法有:
- One-Hot:简单但维度高、稀疏。
- TF-IDF:反映词语在文档中的重要程度。
TF-IDF(t, d) = TF(t, d) * IDF(t),其中TF是词频,IDF是逆文档频率。 - 词嵌入(Word Embedding):如Word2Vec、GloVe、BERT等,将词语映射到低维稠密向量空间,能捕捉语义信息。
模型训练:将标注好的数据(文本转化为特征向量,标签序列)送入CRF等模型进行训练。CRF的优势在于它能考虑整个标签序列的全局最优,而不仅仅是当前词的局部特征。
第三步:服务化部署与持续集成
模型训练好后,需要封装成服务供业务部门使用。一个典型的服务架构如下:
- 数据输入:支持两种方式——定时爬取指定网站;提供API接口接收业务部门提交的文本。
- 数据处理流水线(Pipeline):
- 对输入文本进行清洗和预处理。
- (可选)使用文本分类模型(如SVM、逻辑回归)过滤无关文本。
- 对相关文本调用信息提取模型,抽取出结构化数据。
- 将结果存储到数据库(如MySQL, MongoDB)。
- 前端展示:业务人员可以通过前端页面查询数据库中的结果。
技术栈建议:
- Web框架:使用轻量级的Flask或FastAPI构建RESTful API。
- 任务队列:使用Celery处理异步任务(如爬虫、模型预测)。
- 消息队列:使用RabbitMQ或Redis进行服务间通信。
- 容器化:使用Docker封装服务,便于部署和环境统一。
- 缓存:使用Redis存储临时数据或加速访问。
持续智能(Continuous Intelligence):借鉴软件工程的持续集成/持续交付(CI/CD)理念,将其应用于AI项目。构建一条自动化流水线,当数据、代码或模型更新时,自动触发数据预处理、模型重新训练、评估、测试和部署。这确保了模型能随着新数据的积累而持续迭代优化,形成一个“数据->模型->服务->新数据”的闭环。
总结
本节课我们一起学习了使用Python进行文本结构化信息提取的全流程:
- 概念:我们了解了结构化信息提取是将非结构化文本转化为机器可读数据的关键步骤。
- 价值:它能够辅助商业决策,并为构建智能服务奠定数据基础。
- 方法:我们对比了规则方法、机器学习(重点推荐CRF)和深度学习方法。
- 实施:核心步骤包括数据获取与清洗、分词与标注、模型训练。我们强调了数据质量的重要性。
- 部署:我们探讨了如何将模型构建成可用的服务,并引入了持续智能的理念,以实现模型的自动化迭代和更新。

希望本教程能帮助你入门文本信息提取领域,并着手解决实际业务问题。
004:在30分钟内迁移Python Web应用到无服务器 🚀

概述
在本节课中,我们将学习无服务器计算的核心概念,特别是以AWS Lambda为例,探讨如何将传统的Python Web应用迁移到无服务器架构。我们将了解无服务器为何出现、它的优势与局限,并通过具体案例和代码演示,展示如何快速上手开发无服务器函数。
无服务器计算的出现背景 🤔
上一节我们概述了课程内容,本节中我们来看看无服务器计算为何会出现。
在传统的应用开发中,与业务逻辑直接相关的代码通常不超过40%。开发者需要花费大量时间处理平台、系统集成等非核心功能,这降低了生产效率。随着应用日益复杂、需求变更和发布速度要求加快,这一矛盾愈发突出。
因此,业界期望能有一个平台,它能自动化管理基础设施资源,让开发者专注于业务逻辑。这个平台应具备按使用付费、自动弹性伸缩、高可用性和内置安全等特性。这种需求催生了无服务器计算的概念。
无服务器并非没有服务器,而是服务器细节对开发者透明,由平台负责管理。最初,无服务器主要针对计算平台,但如今其概念已扩展到存储、应用集成等多个领域。
无服务器的发展历程 📈
上一节我们探讨了无服务器出现的动因,本节中我们来看看它的发展历程。
无服务器的发展大致如下:
- 2014年11月,AWS在re:Invent大会上发布Lambda。
- 2015年,业界提出了“Serverless”的名称和概念。
- 2016年初,Google Cloud Functions发布。
- 2016年2月,开源项目OpenWhisk出现。
- 2016年3月,Microsoft Azure Functions发布。
- 2017年2月,开源项目OpenFaaS出现。
- 2017年10月,Google Cloud Functions正式发布。
- 2017年12月,Apache OpenWhisk成为Apache顶级项目。
如今,无服务器产品家族已发展成一个庞大体系,主要分为两大路线:以AWS、Google、Azure为代表的商业化云平台,以及以Kubernetes生态为基础的开源项目体系。
无服务器的优势:一个并行化案例 ⚡
上一节我们回顾了无服务器的发展,本节中我们通过一个案例来具体感受它的优势。
当我们开发分布式应用时,使用复杂框架(如Ray)可能“杀鸡用牛刀”。无服务器提供了一种更简单的并行任务开发模型。
以下是OpenFaaS的一个案例,该项目需要处理用户注册、管理、通知和数据验证等任务,数据量庞大。
传统串行方式效率低,而构建并行架构又太复杂。OpenFaaS使用约12个守护函数、24个函数、12个API接口和7个数据库,构建了一个并行的服务架构。
实现这个架构的开发量其实很小,因为它是基于“搭积木”的方式,用少量代码将各个服务像胶水一样连接起来。这使并行化实现变得非常简单。
AWS Lambda 与 Python 的关系 🐍
上一节我们看到了无服务器如何简化并行任务,本节我们聚焦到具体的服务——AWS Lambda,及其与Python的紧密联系。
Lambda是计算环境的高度抽象,本质上是一个函数开发环境,也称为 FaaS。
函数主要通过事件驱动的方式被调用。事件源非常丰富,包括AWS各种服务的事件,例如对象存储(S3)中文件的上传或删除。
Lambda的常见应用场景包括:
- Web应用:静态网站、博客、图片处理、网页渲染。
- 后端处理:任务调度、系统集成、流程处理。
- 数据处理:构建数据处理管道(Pipeline)。
- 聊天机器人:例如亚马逊Alexa的技能(Skill)后端。
- 运维自动化:日志监控告警、自动响应事件。
- AI推理:例如训练模型生成诗歌。
Lambda就像一个“胶水”或调度器,将各种服务串联在一起。亚马逊Echo智能音箱的所有语音交互都运行在Lambda之上。
如何编写一个 Lambda 函数 ✍️
上一节我们了解了Lambda的用途,本节中我们来看看如何编写一个Lambda函数。
一个基本的Lambda函数结构如下(以Python为例):
def lambda_handler(event, context):
# 你的业务逻辑
return result
event参数:包含触发函数的事件信息,通常是一个字典。context参数:提供函数运行时的上下文信息。- 返回值:可选,返回函数处理结果。
这是一个真实的例子:
def my_handler(event, context):
name = event.get('name', 'World')
greeting = f'Hello, {name}!'
return {'message': greeting}
编写过程很简单,可以在AWS控制台从零开始写,或基于蓝图(blueprint)模板开发。
Lambda 的局限性 ⚠️
上一节我们学会了编写简单的Lambda函数,本节中我们必须了解它当前的局限性。
- 不支持长时间运行:函数最大运行时间为15分钟。
- 冷启动延迟:函数首次调用或一段时间未被调用后,需要时间初始化容器和代码环境。
- 资源限制:内存、临时存储、包大小、并发数等均有上限。
- 工具链变化:需要适应新的IDE、部署、监控和调试工具。
- 运行环境单一:目前主要为通用CPU环境,不支持GPU等特定硬件。
- 供应商锁定:不同平台标准不一,迁移成本较高。
在冷启动方面,不同语言性能差异显著。一个简单的“Hello World”测试显示,Python的启动性能约为Java的400倍,这得益于解释型语言更小的运行时开销。因此,Python是与无服务器结合的理想语言。
Lambda 的具体技术限制与调试 🔧
上一节我们讨论了Lambda的宏观局限,本节我们深入其具体的技术限制和调试方法。
技术规格限制:
- 内存与CPU:128 MB 到 3 GB,按64 MB递增,CPU和网络资源随之分配。
- 超时时间:最长 900秒 (15分钟)。
- 环境变量:最大 4 KB。
- 层(Layers):用于共享代码和依赖库。
- 并发执行:初始限制500-3000,可申请提升。
- 部署包大小:压缩后 50 MB,解压后 250 MB。
- 临时存储(/tmp):512 MB。
调试工具:
使用print调试效率低。推荐使用 AWS X-Ray 进行分布式跟踪。在代码中引入X-Ray SDK,对函数或代码片段进行标注,即可获得详细的性能剖析数据。
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
patch_all() # 自动检测AWS SDK调用
@xray_recorder.capture('my_function_segment')
def lambda_handler(event, context):
# 业务逻辑
result = do_something()
return result
开发与部署工具 🛠️
上一节我们介绍了调试方法,本节我们来看看有哪些高效的开发和部署工具。
开发工具:
主流IDE(如PyCharm, VS Code)都提供了优秀的无服务器开发插件。特别推荐 AWS Toolkit for VS Code,它提供了完整的Lambda函数创建、本地调试、部署和测试支持。
部署工具:
手动编写CloudFormation模板(JSON/YAML)较为繁琐。推荐使用 Serverless Application Model (SAM) 框架及其命令行工具 sam。
sam 是一个基于SAM框架的开源CLI工具,它简化了无服务器应用的开发、测试和部署。
以下是使用 sam 的典型工作流:
- 初始化项目:
sam init - 本地构建与测试:
sam build和sam local invoke - 部署到云端:
sam deploy --guided - 发布到模板仓库:
sam publish
通过几条命令即可完成从开发到上线的全过程,并能使用Docker在本地模拟Lambda环境进行测试。
迁移现有应用到 Lambda 🔄
上一节我们掌握了新项目的开发部署,本节我们探讨如何将现有Python应用迁移到Lambda。
有两种流行的工具可以简化迁移:
-
Zappa:
这是一个非常强大的工具,特别适合部署Flask、Django等WSGI应用。你只需对现有应用做少量修改,Zappa就能将其打包并部署为Lambda函数和API Gateway。# 安装 pip install zappa # 初始化配置 zappa init # 部署 zappa deploy -
Chalice:
这是AWS官方提供的微框架,让你能像编写普通Python Web应用一样编写代码,然后轻松部署到Lambda。它提供了类似Flask的装饰器语法。from chalice import Chalice app = Chalice(app_name='helloworld') @app.route('/') def index(): return {'hello': 'world'}使用Chalice CLI即可部署:
chalice deploy
这些工具大大降低了从传统Web应用到无服务器架构的迁移门槛。
总结
本节课中,我们一起学习了无服务器计算的核心概念与发展历程。我们深入探讨了AWS Lambda的工作原理、优势与局限性,并了解到Python因其快速的冷启动特性,与无服务器架构是天作之合。
我们通过代码示例学习了如何编写Lambda函数,并介绍了X-Ray调试、SAM部署框架等实用工具。最后,我们探讨了使用Zappa和Chalice等工具,将现有Python Web应用快速迁移到无服务器平台的方法。

尽管当前的无服务器技术仍有诸多限制,但其发展方向明确,能显著提升开发敏捷性和资源利用率。鼓励大家积极实践,探索无服务器与Python结合带来的更多可能性。
005:交互式数据可视化入门

概述
在本节课中,我们将学习交互式数据可视化的概念、其重要性,并重点介绍一个名为Dash的Python框架。我们将了解如何利用Dash轻松构建交互式Web应用,以更好地理解和探索数据。
交互式数据可视化的优势
上一节我们介绍了课程概述,本节中我们来看看交互式数据可视化能带来哪些具体好处。
我使用了两组数据:日本的外国游客数量以及京都的酒店数量。第一张表格显示了按国家划分的日本外国游客数量,中国位列第一。通过下拉菜单选择不同地区,可以看到数据动态变化。
第二张图表展示了日本游客总数以及所选国家游客的占比。例如,中国游客的占比正逐月增长,近期已接近40%。与此同时,日本游客总数自2014年起持续增长,五年内增长了两倍。
这导致京都的酒店数量在2014年至2017年间急剧增加。为了做出投资决策,我创建了一个包含地图的交互式可视化应用。地图显示了京都酒店的位置,帮助区分热门与冷门地段。
这个例子表明,交互式可视化能提供更多信息,帮助我们理解环境背景。它对于商业决策或个人生活服务规划都至关重要。
为何需要进行数据可视化
在了解了交互式可视化的优势后,我们来看看数据可视化在数据分析流程中的核心作用。
数据分析通常包含五个步骤:
- 定义问题和目标
- 收集数据
- 数据预处理
- 探索性数据分析
- 评估模型和算法
在第四步“探索性数据分析”中,我们需要分析数据集并总结其主要特征。这一步常使用可视化方法,因为人类通过视觉观察数据效率更高。此步骤对于构建优质模型至关重要。
如果仅使用数据表格,例如一个包含115行数据的房地产数据集,大多数人很难理解数据的模式或特征。相比之下,图表能让我们更直观地理解数据的分布和特点。
因此,数据可视化是帮助我们理解数据的关键工具。
常见的数据可视化工具
在明确了可视化的必要性后,以下是一些常用的数据可视化工具:
- 商业智能工具:例如 Tableau、Microsoft Power BI、Google Data Studio。
- 开源库:例如 Matplotlib、Bokeh。
- 交互式Web框架:例如 Shiny (R)、Dash (Python)、Panel。
这些工具各有优势。我个人常使用Dash和Plotly,但也会根据需求使用Matplotlib和Tableau。今天我们将重点介绍Dash。
Dash框架简介
Dash是一个基于Python的开源框架,由Plotly公司创建。它允许开发者仅使用Python代码来构建交互式Web应用。这一点非常重要,因为它意味着数据分析的所有步骤都可以在同一种语言环境中完成。
Dash基于Flask、React和Plotly构建,其官方文档非常详尽且易于理解。使用Dash可以轻松构建交互式数据可视化应用,并易于分享。
除了图表,Dash还包含许多其他组件,例如用于表格的dash-table,用于生物信息学可视化的dash-bio。你甚至可以构建自己的自定义组件。
如何构建Dash应用
本节我们将学习构建Dash应用的基本方法。一个典型的Dash应用由两部分构成:布局和回调函数。
- 布局:定义了应用的外观,即用户看到的界面元素。
- 回调函数:使应用具有交互性,它定义了当用户与界面元素(如下拉菜单、滑块)互动时,数据如何更新以及图表如何响应。
以下是一个简单的Dash应用结构示例:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Dropdown(id='my-dropdown', options=[...], value='初始值'),
dcc.Graph(id='my-graph')
])
@app.callback(
Output('my-graph', 'figure'),
[Input('my-dropdown', 'value')]
)
def update_graph(selected_value):
# 根据 selected_value 过滤或处理数据
# 创建并返回一个图形对象
figure = {...}
return figure
if __name__ == '__main__':
app.run_server(debug=True)
在这个例子中,当下拉菜单的选项改变时,会触发update_graph回调函数,从而更新图形。一个功能完整的交互式应用可能只需几十行代码。
Dash的图表组件
Dash与Plotly紧密集成,可以轻松创建交互式图表。Plotly提供了几种不同的接口:
- Plotly Graph Objects:提供对图表最大程度的控制,代码相对详细。
- Plotly Express:高级封装接口,可以用非常简洁的代码快速创建常见图表。
- Dash:在Plotly图表基础上,增加了Web布局和交互回调能力。
通常,对于快速原型开发,可以使用Plotly Express。当需要更复杂的定制时,则使用Plotly Graph Objects。Dash则负责将图表嵌入到完整的Web应用中并管理其交互逻辑。
其他有用的Dash组件
除了核心图表,Dash生态系统还提供了许多专用组件。以下是其中几个例子:
dash-table:用于创建功能丰富、可交互的数据表格,支持排序、筛选和编辑。dash-canvas:用于图像处理和标注的绘图组件。例如,可以上传图片,在上面画线或移除背景。dash-cytoscape:用于网络图可视化的组件。它可以展示复杂的网络结构,并且改变布局算法会呈现出完全不同的网络视角,有助于发现中心节点或群落结构。
这些组件极大地扩展了Dash的应用场景。
部署与分享Dash应用
构建好应用后,可以轻松地将其部署到云端进行分享。本次演示中,我使用了微软的Azure平台进行部署。
部署步骤非常简单:
- 创建Azure账户并获取部署密钥。
- 创建包含Dash应用代码的主文件(如
app.py)。 - 创建列出项目依赖的
requirements.txt文件。 - 使用Azure CLI(命令行工具)进行部署,通常只需几条命令。
部署成功后,任何有链接的人都可以访问你的交互式应用。在日本,Azure为新用户提供一年的免费额度。
分享交互式可视化应用可以促进团队协作,让更多人参与数据探索,从而可能催生新的发现和见解,帮助我们创建更好的服务。
总结

本节课我们一起学习了交互式数据可视化的核心概念。我们了解到交互式可视化能提供更丰富的信息,辅助决策。随后,我们介绍了Dash这一强大的Python框架,它让我们能够用纯Python代码构建和部署交互式Web应用。通过Dash,我们可以轻松创建图表、表格乃至复杂的网络可视化,并与他人分享协作,共同从数据中发现价值,创造更好的服务。现在,就让我们开始用Dash探索数据吧。
006:智能问答简介与分类 🧠

在本节课中,我们将要学习什么是智能问答,以及业界如何对智能问答领域进行分类。
智能问答是人工智能的一个重要应用方向,旨在让机器能够理解人类用自然语言提出的问题,并给出准确的答案。根据知识组织形式和任务目标的不同,智能问答可以分为多个子领域。
以下是几种主要的智能问答类型:
- 基于知识图谱的问答:知识以(实体,关系,实体) 三元组的形式组织。例如,当用户提问“姚明的妻子是谁?”时,系统能定位到三元组 (姚明,妻子,叶莉) 并返回答案“叶莉”。
- 基于表格的问答:知识以结构化表格形式存储在数据库中。系统将用户问题转化为SQL查询语句,从表中检索答案。例如,查询“30元流量套餐包含多少流量?”。
- 阅读理解式问答:给定一篇文档,系统定位并抽取文档中能回答用户问题的片段。例如,在文章中找到“某事件发生的时间”。
- 基于社区知识的问答:这是本节课的重点。它处理的是从论坛、社区等场景中沉淀下来的问答对。例如,在母婴论坛中,“宝宝发烧怎么办?”和“要吃药吗?”这类问题及其回答,可以被组织成可复用的知识。
我们选择基于社区知识的问答作为切入点,因为它易于维护,能显著减少人工维护答案的工作量,并且积累的多样化问法(相似问)是宝贵的训练数据。
Python智能问答之路:2:业务场景与工具选择 🛠️
上一节我们介绍了智能问答的基本概念,本节中我们来看看智能问答具体应用在哪些业务场景,以及为什么选择Python作为开发工具。
智能问答机器人已广泛应用于多个领域,主要分为两大类:
以下是两种核心的业务场景:
- 营销场景机器人:这类机器人不仅是答疑工具,更是商机捕捉助手。例如,用户在公众号咨询课程详情时,机器人不仅能回答问题,还能引导用户留下联系方式,帮助商家转化潜在客户。它是商务团队的好帮手,可以实现跨平台服务,以较少的人力投入获取更多有效线索。
- 客服场景机器人:这是更通用的应用。机器人可以7x24小时在线,快速、准确地回答关于产品、业务或技术经验的问题。对于重复性或基础性问题,它能极大解放人力。这类机器人易于复制到不同企业的相似领域(如HR问答),实现规模化应用。
明确了业务价值后,我们需要选择合适的开发工具。选择依据主要有两点:迭代速度和生态完备性。
以下是几种语言的特性对比与选择理由:
- C++:性能高,但开发迭代慢,机器学习库生态相对较弱。
- Java:工程化能力强,但在快速实验和模型迭代方面不够灵活。
- Python:语法简洁,拥有极其丰富且成熟的机器学习/深度学习库(如scikit-learn, TensorFlow, PyTorch),能极大加速实验和原型开发。
因此,为了快速搭建并持续优化问答机器人,我们选择Python作为主要开发语言。
Python智能问答之路:3:从零搭建问答机器人 🚀
上一节我们确定了业务场景和开发语言,本节中我们来看看如何从零开始,一步步搭建一个可用的问答机器人。
我们的目标是:在已有“知识库”(即一系列问答对)的前提下,当用户提出问题时,系统能快速找到最相关的答案。整个流程可以拆解为检索和匹配两个核心步骤。
以下是首次建模的简单流程:
- 检索:将知识库中的每个“知识点”(包含多个相似问和一个答案)作为一个文档存入Elasticsearch。用户提问时,ES返回最相关的若干个文档,得到候选答案集合。
- 匹配:计算用户问题与ES返回的每个“相似问”之间的语义相关性。首次尝试使用了WMD算法。最终,选择相关性最高的答案返回。
通过 Elasticsearch + WMD 的组合,我们快速实现了一个基础版的问答模型。虽然效果粗糙(相似意图区分能力弱),但验证了流程的可行性,开发效率非常高。
Python智能问答之路:4:模型迭代与优化 📈
上一节我们完成了一个简单的问答模型,本节中我们来看看如何通过多次迭代来持续提升它的效果。
第一次建模的效果并不理想,核心问题是匹配特征单一(仅WMD)。因此,我们开始引入更丰富的特征和复杂的模型进行迭代优化。
以下是四次关键的模型迭代过程:
- 第二次迭代 - 引入排序模型:在检索结果的基础上,引入多种人工设计的统计特征(如Jaccard相似度、编辑距离、词频等)和词向量特征(Word2Vec),训练一个逻辑回归排序模型,对候选答案进行更精细的排序。
- 第三次迭代 - 利用知识库数据:使用知识库内的问答对数据训练一个FastText文本分类模型,并提取其词向量。新增FastText分类概率和词向量相似度作为新特征。
- 第四次迭代 - 引入深度学习模型:训练一个ESIM深度学习模型来更好地捕捉句子间的交互信息,并改进其词嵌入层。新增ESIM模型的匹配分数作为特征。
在整个迭代过程中,Python的数据处理(如网络抓取、文本清洗)和特征计算(利用scikit-learn、gensim、FastText等库)提供了极大的便利。通过分析每次迭代产生的错误案例,我们能够有针对性地设计新特征或引入新模型。
最终,在同样的测试集上,系统的准确率从最初的0.8提升到了0.932。
Python智能问答之路:5:效果评估与服务上线 ✅
上一节我们通过迭代优化了模型效果,本节中我们来看看如何科学地评估效果,以及如何将服务部署上线。
模型优化不能只凭感觉,需要有量化的评估指标。我们采用了标准的分类评估方法。
我们的评估策略是:选取6个不同领域,每个领域采样50个知识点,每个知识点用12个相似问做训练,3个做测试。观察每次迭代后模型在测试集上的准确率、召回率和F1值的变化。结果显示,准确率从0.8稳步提升至0.932。
为了追求极致效果,我们尝试引入更先进的预训练模型。例如,使用BERT微调后,准确率进一步提升到了0.968,获得了显著的性能增益。
效果达标后,下一步是上线。在架构设计上,我们采用了微服务模式。
以下是两种架构的对比与我们的选择:
- 单体服务:将所有特征计算和匹配逻辑放在一个服务中。优点是简单,但资源利用不灵活,难以监控和扩容。
- 微服务架构:将不同的特征计算模块拆分为独立的微服务。我们选择了这种架构,因为它能降低单个服务资源占用、方便扩容、易于监控,并且通过gRPC进行服务间通信,减少了连接开销。
最终上线的问答服务,平均响应时间能控制在150毫秒以内,满足线上实时交互的需求。
Python智能问答之路:6:Python的利与弊 ⚖️
上一节我们完成了服务的上线,本节中我们一起来总结在整个开发过程中,Python展现出的优势与面临的挑战。
Python让我们能够快速搭建和迭代智能问答系统,这主要得益于它的核心优势。
以下是Python带来的主要好处:
- 开发迭代速度快:语法简洁,库丰富,能快速实现想法并验证。
- 强大的第三方库生态:从数据处理到模型训练,都有成熟工具包支持。
- 胶水语言特性:可以方便地调用C/C++或Java代码,弥补自身性能短板。
然而,在高性能、高并发的生产环境中,Python的一些固有缺点也会暴露出来。
以下是实践中遇到的三个典型问题及解决方案:
- 内存占用高:加载大词典(如TF-IDF)时,Python字典内存开销巨大。解决方案:使用C++编写高效的数据结构(如Key-Value存储),编译成SO文件供Python调用,内存消耗降至1/5。
- 序列化缓慢:微服务间传输大量数据时,Python原生序列化(如pickle)效率低。解决方案:使用支持C++插件的序列化库,性能可提升10倍以上。
- 并发能力有限:由于GIL锁,多线程无法实现计算并行。多进程开销大,协程对计算密集型任务效果不佳。解决方案:通过微服务拆分,将计算密集型特征分散到独立服务中,利用协程进行IO并发调用,提升整体吞吐量。
Python智能问答之路:7:总结与展望 🌟
本节课中我们一起学习了使用Python搭建智能问答机器人的完整路径。
我们来回顾一下核心要点:首先,要对问题(基于社区知识的问答)进行合适的建模。其次,选择Python作为开发语言,快速实现第一个可运行版本。然后,遵循由浅入深的原则,建立“数据->模型->反馈”的闭环,持续迭代优化。最后,保持学习,拥抱新技术(如BERT)来提升效果。同时,要认识到Python的优缺点,在必要时用其他语言进行性能补充。
对于未来,我们有两小点期望:一是希望Python自身的性能能得到持续优化;二是希望其原生支持更好的高并发计算模型。

总结:使用Python开发智能问答系统是一个“快速原型、持续迭代、生态互补”的过程。它让我们能专注于算法和业务逻辑,高效地将想法转化为现实,是入门和实践AI应用的优秀选择。
007:理解 Python AST


在本教程中,我们将学习 Python 抽象语法树(AST)的核心概念、相关库以及如何利用 AST 解决实际问题。我们将从 Python 代码的编译过程开始,逐步深入到 AST 的操作与应用。
概述:Python 代码的编译过程
上一节我们介绍了教程的整体结构,本节中我们来看看 Python 代码是如何从文本变成可执行字节码的。这个过程称为编译(compile),它包含多个步骤。
Python 的编译过程大致如下:
- 源码(Source Code):即我们编写的
.py文件中的文本。 - 解析树(Parse Tree):解析器(Parser)将源码转换成一棵保留了所有语法细节(如空格、换行)的树。
- 抽象语法树(AST):解析树被进一步抽象,只保留程序的结构和逻辑,忽略格式细节。这是我们本节课的重点。
- 控制流图(CFG):AST 被转换为控制流图,用于描述程序执行的路径。
- 字节码(Bytecode):最后,控制流图被转换为 Python 解释器可以直接执行的字节码(即
.pyc文件中的内容)。
整个过程可以简化为:源码 -> 解析树 -> AST -> CFG -> 字节码。
深入解析与 AST 生成
上一节我们了解了编译的宏观流程,本节中我们来看看 解析树 到 AST 转换的具体步骤。
这个过程主要由 Python 内部的 C 代码实现,非常复杂。我们可以将其简化为以下几步:
- 读取解析树:从解析树中提取信息。
- 应用转换规则:根据 Python 的语法定义(位于
Python.asdl文件中),将解析树节点映射为 AST 节点。 - 生成 AST 节点:创建代表不同语法结构的 AST 节点(如
FunctionDef,ClassDef,Assign等)。 - 后续处理:生成的 AST 会进一步被处理成控制流图(CFG),最终用于生成字节码。
Python AST 核心库介绍
了解了 AST 的生成过程后,我们需要工具来处理它。Python 标准库提供了 ast 模块。
ast 模块是 Python 自带的库,用于处理抽象语法树。它的官方文档非常简洁,主要说明了以下几点:
- AST 可用于分析和修改 Python 的语法树。
- AST 节点可以被 Python 内置的
compile()函数直接编译,这意味着我们可以通过修改 AST 来改变程序的行为(但会绕过一些检查)。 - 模块提供了
ast.NodeVisitor等类来遍历 AST。
由于官方文档信息有限,社区有一个更详细的指南 Green Tree Snakes - the missing Python AST docs,它补充了更多关于节点类型和用法的示例。
以下是对初学者最重要的两个概念:
- 遍历 AST:通过继承
ast.NodeVisitor类并实现visit_节点类型()方法(例如visit_FunctionDef),可以方便地遍历和访问特定类型的 AST 节点。 - 修改 AST:在访问器方法中,可以修改或替换节点。如果想删除一个节点,只需在该方法中返回
None。
然而,ast 模块有一个明显的不足:它只能将代码解析为 AST,却不能将 AST 美观地转换回源代码。为此,我们需要第三方库。
强大的第三方库:astor
上一节提到 ast 模块无法将 AST 转回代码,本节中我们来看看一个解决此问题的强大工具:astor。
astor 库弥补了 ast 模块的不足,主要功能包括:
- 将 AST 反编译为源代码:通过
astor.to_source()函数,可以将 AST 对象转换回格式良好的 Python 代码字符串。 - 美化打印 AST:
astor.dump_tree()可以输出结构清晰、易于阅读的 AST 树形表示,便于调试。 - 提供非递归的 AST 遍历工具。
它的使用非常简单:
import ast
import astor
code = “print(‘hello’)”
tree = ast.parse(code) # 将代码解析为 AST
source = astor.to_source(tree) # 将 AST 转换回代码
print(source) # 输出:print(‘hello’)
实战案例一:安全替换所有 print 语句
学习了基础工具后,我们来看一个实际应用场景。假设项目中有大量用于调试的 print 语句,我们想将它们全部替换为更规范的日志调用(例如 logging.info),但又担心手动替换出错或漏掉。
使用正则表达式替换 print 是危险的,因为它可能错误地修改了函数名、字符串内容等。AST 可以精确地定位函数调用节点。
解决思路是“找不同”:
- 分别将
print(‘debug’)和logging.info(‘debug’)解析成 AST。 - 对比两个 AST 的结构差异。
- 编写一个
NodeTransformer子类,在遍历 AST 时,将匹配的print调用节点替换为logging.info调用节点。 - 利用
astor.to_source()生成替换后的新代码。
关键步骤的代码逻辑如下:
import ast
import astor
class PrintToLogTransformer(ast.NodeTransformer):
def visit_Call(self, node):
# 检查是否是 print 函数调用
if isinstance(node.func, ast.Name) and node.func.id == ‘print’:
# 构建新的 logging.info 调用节点
new_node = ast.Call(
func=ast.Attribute(value=ast.Name(id=‘logging’, ctx=ast.Load()),
attr=‘info’,
ctx=ast.Load()),
args=node.args,
keywords=[]
)
return new_node
return node
# 使用转换器
code = “print(‘hello’)”
tree = ast.parse(code)
transformer = PrintToLogTransformer()
new_tree = transformer.visit(tree)
new_code = astor.to_source(new_tree)
print(new_code) # 输出:logging.info(‘hello’)
注意:实际应用中还需处理 import logging 语句的添加。
实战案例二:智能合并生成的代码
现在来看一个更复杂的真实场景。假设我们通过模板自动生成 RPC 客户端代码。某天需要在某个生成的方法里添加一个默认参数。如果直接修改生成的代码,下次重新生成时修改会被覆盖。如果修改模板,则所有方法都会受到影响。
我们希望:重新生成代码时,只添加新方法,并保留对旧方法的手动修改。
AST 可以完美解决这个问题:
- 分析现有代码:解析已存在的客户端代码 AST,遍历并收集所有已有的方法名。
- 对比差异:与最新的接口定义(IDL)进行比较,找出新增的方法。
- 生成并合并:只为新方法生成代码 AST 节点,并将其插入到现有代码 AST 的类定义体中。
- 输出最终代码:将合并后的 AST 写回源文件。
以下是核心逻辑的简化示例:
import ast
class CodeMerger(ast.NodeVisitor):
def __init__(self, new_methods):
self.existing_methods = []
self.new_methods_nodes = new_methods # 新增方法对应的 AST 节点列表
def visit_ClassDef(self, node):
# 收集现有方法名
for item in node.body:
if isinstance(item, ast.FunctionDef):
self.existing_methods.append(item.name)
# 将新增的方法节点添加到类体中
node.body.extend(self.new_methods_nodes)
return node
# 假设 old_tree 是旧代码的 AST,new_methods_nodes 是新方法 AST 节点列表
merger = CodeMerger(new_methods_nodes)
updated_tree = merger.visit(old_tree)
# 然后用 astor.to_source 写回文件
通过这种方式,我们实现了非侵入式的代码更新,既保留了手工修改,又增加了新功能。
总结与最佳实践
本节课中我们一起学习了 Python AST 的方方面面。我们来总结一下关键点和最佳实践。
核心要点总结:
- AST 是什么:AST 是源代码抽象语法结构的树状表示,它忽略了代码的格式细节,只关注逻辑结构。
- 核心工具:
ast模块用于解析和操作 AST,astor库用于将 AST 转换回可读的源代码。 - 核心操作:通过继承
ast.NodeVisitor(遍历)或ast.NodeTransformer(修改)来访问和操作 AST 节点。 - 解决问题:AST 擅长解决需要精确语法分析的问题,例如安全地重构代码、分析代码结构、智能合并等。
给初学者的建议:
- 正则表达式 vs AST:当需要处理具有语法结构的文本(如 Python 代码)时,AST 比正则表达式更精确、更安全。正则表达式处理的是纯文本,容易误匹配。
- AST 操作三步法:当需要修改代码时,可以遵循“找不同 -> 写遍历/转换器 -> 复制粘贴节点”的模式。先用
astor.to_source和对比工具观察差异,再编写转换逻辑。 - 谨慎编译:虽然可以直接编译修改后的 AST,但这绕过了 Python 的某些前端检查。应确保生成的 AST 结构正确,最好通过
astor.to_source输出代码进行验证。 - 举一反三:AST 并非 Python 独有,大多数编程语言都有类似的概念和工具。理解 AST 有助于你深入理解程序编译和静态分析。
希望本教程能帮助你理解并开始使用 Python AST 这个强大的工具。


008:FPGA 与 Python 的融合之路

在本教程中,我们将学习如何利用 FPGA(现场可编程门阵列)来加速 Python 计算。我们将从 FPGA 的基本概念讲起,逐步深入到它与 Python 结合的框架和实际应用案例,旨在让初学者也能理解这一硬件加速技术。
概述
FPGA 是一种可编程的硬件芯片,能够通过并行计算显著提升特定任务的执行速度。对于 Python 开发者而言,这意味着可以将计算密集型任务(如图像处理、AI推理)从 CPU 转移到 FPGA 上,从而获得性能飞跃。本节课我们将探讨 FPGA 的工作原理、它与传统计算的区别,以及如何通过特定工具链将 Python 代码映射到 FPGA 硬件上运行。
FPGA 基础概念
上一节我们概述了 FPGA 加速的潜力,本节中我们来深入了解 FPGA 是什么。
FPGA 的全称是 现场可编程门阵列。它本质上是一个由大量逻辑门(如与门、或门、非门、触发器等)组成的芯片,用户可以通过硬件描述语言(HDL)来定义这些逻辑门之间的连接关系,从而“编程”出特定的硬件电路。
与 CPU 或 GPU 的固定架构不同,FPGA 的硬件结构是可重构的。你可以为特定算法(例如图像滤波或矩阵乘法)定制一个专用的硬件电路。这个电路可以并行处理数据,而不像 CPU 那样需要顺序执行指令。
核心优势:并行计算
CPU 执行任务通常是串行的,一个时钟周期完成一步操作。而 FPGA 可以将一个任务分解成多个独立的步骤,并搭建一个流水线或并行阵列电路。
- 流水线:类似于工厂的装配线,数据流经多个处理阶段,每个时钟周期都能输出一个结果。
- 并行阵列:复制多个相同的处理单元,同时处理多份数据。
例如,一个包含 10 步的运算:
- 在 1GHz 的 CPU 上串行执行,可能需要 10 个时钟周期。
- 在 200MHz 的 FPGA 上,如果设计成 10 级流水线,则每个时钟周期都能输出一个运算结果,等效吞吐率可能远高于 CPU。
公式化理解:
对于处理 N 个数据项的算法:
- CPU(串行)耗时 ≈ N × (算法步骤数 / CPU 主频)
- FPGA(全并行)耗时 ≈ (算法步骤数 / FPGA 主频) + (N-1) × 流水线延迟
当 N 很大时,FPGA 的吞吐量优势非常明显。
从软件算法到硬件电路
理解了 FPGA 的并行本质后,我们来看看如何将软件算法转化为硬件电路。传统上,这需要工程师使用 Verilog 或 VHDL 等硬件描述语言进行设计,门槛较高。
为了让软件工程师(尤其是 C/C++ 开发者)也能利用 FPGA,赛灵思(Xilinx)推出了 高层次综合 工具。
HLS 允许开发者使用 C、C++ 或 OpenCL 等高级语言编写算法代码,然后由工具自动分析、优化并综合出对应的硬件描述代码(如 Verilog)。开发者需要以硬件设计的思路来编写 C 代码,例如:
- 明确指示循环是展开并行执行还是流水线执行。
- 精确指定数据存储在哪种硬件资源中(如 Block RAM、分布式 RAM)。
- 考虑数据流和接口时序。
代码示例对比:
纯软件 C 代码可能这样写一个累加:
int sum = 0;
for(int i = 0; i < 100; i++) {
sum += array[i];
}
在 HLS 中,为了实现并行加速,可能会添加编译指示(pragma)来展开循环:
int sum = 0;
#pragma HLS UNROLL factor=4
for(int i = 0; i < 100; i++) {
sum += array[i]; // 工具会尝试创建4个加法器并行计算
}
HLS 工具会分析代码,在资源(逻辑门数量)和性能(计算速度)之间取得平衡,生成最优的硬件实现。
Python 与 FPGA 的桥梁:Pynq 框架
前面我们介绍了用 C 语言通过 HLS 开发 FPGA 功能,但对于 Python 社区,有一个更直接的框架——Pynq。
Pynq 是一个开源框架,它的核心思想是“Python 生产力遇上赛灵思可编程硬件”。它允许 Python 开发者直接调用预先封装好的硬件加速模块,而无需深入底层硬件设计。
Pynq 的工作原理:
- Overlay:这是 Pynq 的核心概念。Overlay 是一个包含预设计硬件加速模块(IP核)的比特流文件,加载到 FPGA 后,就配置出了特定的硬件电路。你可以把它理解为一个“硬件库”或“硬件驱动”。
- Python API:Pynq 为 Overlay 中的每个硬件模块提供了对应的 Python 类和方法。开发者像调用普通 Python 库一样调用这些方法,实际计算则在 FPGA 上执行。
- 软硬件协同:Pynq 运行在集成了 ARM 处理器和 FPGA 的 SoC 芯片上。Python 和操作系统运行在 ARM 处理器上,而计算密集型任务被分派给 FPGA 加速。
开发流程:
- 硬件工程师:使用 HLS 或传统 HDL 设计加速算法,并封装成 Overlay。
- 系统工程师:将 Overlay 集成到 Pynq 镜像中,并提供 Python API。
- Python 应用开发者:在 Jupyter Notebook 或其他环境中,导入 Pynq 库,加载 Overlay,调用 API 完成加速计算。
这种分工使得算法专家可以专注于 Python 层面的应用开发,无需成为硬件专家。
实战案例:光流计算加速
让我们通过一个具体案例——光流计算,来感受 FPGA 的加速效果。光流是计算机视觉中用于追踪物体运动的算法,计算量很大。
场景对比:
- 纯软件实现:在 ARM Cortex-A53 CPU 上运行 OpenCV 的光流算法。
- 硬件加速实现:使用 Pynq,将光流算法的主要部分在 FPGA 上执行。
以下是性能对比的简化表示:
| 实现方式 | 处理分辨率 | 帧率 (FPS) | 备注 |
|---|---|---|---|
| 纯软件 (CPU) | 640x480 | ~5 FPS | 难以满足实时性要求 |
| Pynq+FPGA | 640x480 | ~60 FPS | 达到实时处理标准 |
可以看到,通过 FPGA 加速,性能提升了一个数量级,实现了实时处理。
代码片段对比:
在 Pynq 中,使用硬件加速的代码可能与软件版本非常相似。
纯 Python (CPU) 计算可能类似:
import cv2
# ... 读取图像等准备工作
flow = cv2.calcOpticalFlowFarneback(prev_gray, next_gray, None, ...)
使用 Pynq 和预置的硬件 Overlay 后,代码可能变为:
from pynq import Overlay
from pynq.lib.video import *
overlay = Overlay("optical_flow.bit") # 加载包含光流加速器的硬件配置
hw_accelerator = overlay.optical_flow_0 # 获取加速器实例
# 将图像数据送入FPGA的特定内存区域
hw_accelerator.write_frame(prev_gray_data)
hw_accelerator.write_frame(next_gray_data)
# 启动硬件计算
hw_accelerator.start()
flow_data = hw_accelerator.read_result() # 从FPGA读取结果
虽然需要管理数据在主机内存和 FPGA 内存之间的传输,但核心计算调用被大大简化,且速度极快。
生态系统与应用领域
Pynq 和赛灵思的 FPGA 平台已经构建了活跃的生态系统,支持多种应用:
- 计算机视觉:如前所述,是 FPGA 的传统优势领域。OpenCV 的许多函数都有硬件加速版本。
- 人工智能推理:在边缘设备上部署神经网络。赛灵思提供 Vitis AI 开发套件,能高效地将 TensorFlow、PyTorch 等框架训练的模型部署到 FPGA 上进行低功耗、低延迟推理。
- 数据中心加速:赛灵思的 Alveo 加速卡可以插入服务器,用于加速数据库查询、金融风险分析、视频转码等云端工作负载。同样支持类似 PyTorch 的框架进行开发。
- 工业与边缘计算:例如电机的预测性维护,通过 FPGA 实时分析传感器数据,并运行 AI 模型判断设备状态。
对于初学者和开发者,可以从 Pynq-Z2 等开发板入手。这些板卡价格适中,集成了处理系统和 FPGA,预装了 Pynq 环境,可以直接在 Jupyter Notebook 中开始 Python 编程和硬件加速实验。
总结
在本节课中,我们一起学习了 FPGA 如何助力 Python 加速计算。
- FPGA 基础:我们了解了 FPGA 是一种可编程硬件,通过并行计算和流水线技术,能为特定算法提供远超 CPU 的吞吐效率。
- 开发演进:从复杂的硬件描述语言到高层次综合,再到 Pynq 框架,开发门槛不断降低,使得 Python 开发者也能利用 FPGA 的强大算力。
- 核心机制:Pynq 通过 Overlay 的概念,将硬件加速模块封装成可调用的 Python 接口,实现了软硬件的无缝协同。
- 显著收益:通过光流计算等案例,我们看到了 FPGA 带来的数量级性能提升,使实时处理成为可能。
- 广阔生态:FPGA 加速技术已广泛应用于计算机视觉、AI 推理、边缘计算和数据中心等多个领域,拥有成熟的工具链和社区支持。

对于希望突破纯软件性能瓶颈,特别是在嵌入式、边缘计算和实时处理领域深耕的 Python 开发者而言,学习使用 FPGA 加速是一项极具价值的前沿技能。
009:类型检查拯救粗心开发者 🛡️

在本节课中,我们将要学习Python中的可选类型标注功能,了解它如何帮助开发者在编写代码时提前发现因粗心导致的类型错误,从而提升代码的健壮性和开发效率。
概述
你是否在开发中经常遇到类似“AttributeError”或“TypeError”这样的错误?这些错误往往源于一些简单的疏忽,例如变量意外变成了None,或者忘记了必要的类型转换。传统上,我们需要运行代码甚至部署到服务器后才能发现这些问题,过程繁琐且耗时。
幸运的是,从Python 3.5开始,Python引入了可选类型标注。这为Python带来了静态类型检查的能力,允许我们在保存代码时,甚至在运行之前,就发现潜在的类型错误。本节课将介绍类型标注的基本用法、如何利用类型检查器,以及它如何帮助我们避免常见的“十亿美元错误”。
常见问题与类型错误的困扰
上一节我们概述了类型检查的价值,本节中我们来看看开发中具体会遇到哪些因类型疏忽导致的错误。
在编写业务逻辑时,我们可能写出看似正确的代码。例如,一个处理Web请求的视图函数,或者一个检查文件后缀的简单函数。然而,一旦运行,就可能遇到各种AttributeError或TypeError。
这些错误通常源于一些微小的疏忽:
- 一个本应是整数的参数,忘记进行类型转换。
- 在函数调用时,不小心多写或少写了一层括号,导致传递的参数结构错误。
这类错误非常初级,但出现频率高,需要反复“修改-运行”来调试,严重影响了开发效率。我曾经以为Python编程就是如此,直到我接触了静态类型语言的理念:“如果它能通过类型检查,它就能够运行。” 这是一种理想状态,意味着在代码运行前就能保证其类型安全。
Python的可选类型标注
上一节我们看到了动态类型带来的困扰,本节中我们来看看Python提供的解决方案:可选类型标注。
好消息是,Python通过PEP标准引入了类型标注语法,使得静态类型检查成为可能。虽然不如Haskell等语言强大,但已足够实用。
我们只需在函数签名等处增加类型标注。例如,一个函数可能被意外地传入了错误数量的参数:
def process_data(data: list, threshold: int) -> list:
# ... 函数逻辑
当我们使用类型检查器(如mypy)分析这段代码时,如果调用时传入了不匹配的参数数量或类型,检查器会立即报错,而无需实际运行代码。这让我们能快速定位并修复诸如括号错误等疏忽。
解决“None”相关问题
上一节我们介绍了基础类型标注,本节中我们来看看如何用它解决一个经典难题:None值处理,即所谓的“十亿美元错误”。
在其他语言中,空指针异常很常见。在Python中,我们经常编写可能返回None的函数。调用者很容易忘记检查返回值是否为None,从而在访问属性时导致运行时错误。
使用类型标注,我们可以用Optional[Type]来明确表示一个值可能为None。
from typing import Optional
def find_user(user_id: int) -> Optional[User]:
# 如果找到,返回User对象;否则返回None
...
# 调用时
user = find_user(123)
# 此时,类型检查器知道`user`是 Optional[User] 类型
# 如果直接写 user.name,检查器会提示错误,因为user可能为None
这样,类型检查器会强制我们在访问user的属性前,先检查它是否为None。通过这种方式,只要代码通过了类型检查,就意味着所有潜在的None值都已被妥善处理,我们不会再忘记检查它们。
主流类型检查器介绍
上一节我们学会了用Optional处理空值,本节中我们来了解一下有哪些工具可以执行静态类型检查。
由于Python的类型标注是标准化的,因此有多种类型检查器可供选择。以下是几个主流的选择:
以下是几个主流的Python类型检查器:
- mypy:最知名、被官方推荐的类型检查器,有Python核心开发者参与开发。它允许部分代码标注,对现有项目非常友好。
- Pyre:由Facebook用OCaml编写,以检查速度快著称。
- pytype:由Google开发。
- Pylance:由微软用TypeScript编写,通常作为VSCode的插件提供强大的语言服务。
目前,我主要使用mypy,因为它支持渐进式类型标注,允许项目逐步迁移。你可以根据自己的喜好和项目需求进行选择。所有检查器都遵循相同的PEP标准,因此不必担心兼容性问题。
何时使用类型标注
上一节我们了解了各种检查器,本节中我们来探讨一下应该在什么情况下使用这一特性。
类型标注是Python的可选特性,因此需要根据情况判断是否使用。我的个人经验是:
以下是一些使用类型标注的时机建议:
- 不建议标注的情况:代码非常简单(如几十行)、一次性使用的脚本。此时标注的收益可能小于成本。
- 强烈建议标注的情况:处理复杂项目时,尤其是在进行代码重构时。重构时我们专注于业务逻辑,很容易忽略函数参数和返回值的类型约定。此时,类型标注能提供安全保障。
- 渐进式采用:你不需要一次性为整个项目添加类型标注。可以采取渐进式策略,先从核心模块或新代码开始,逐步让项目类型越来越完善,平滑地提升代码质量。
总结

本节课中我们一起学习了Python的可选类型标注如何成为“粗心开发者”的救星。我们首先回顾了动态类型语言中常见的因疏忽导致的运行时错误。然后,我们介绍了如何通过为函数和变量添加类型标注,来明确代码的契约。接着,我们深入探讨了使用Optional类型来安全处理None值,避免空指针错误。我们还了解了几种主流的类型检查器工具,如mypy。最后,我们讨论了在实际项目中渐进式采用类型标注的策略。通过利用类型检查,我们可以在代码运行前就捕获大量低级错误,从而提升开发效率与代码可靠性。
010:图算法在反欺诈中的应用与实现 🕵️♂️

在本节课中,我们将学习图算法的基本概念,并了解其在反欺诈领域的具体应用与实现方法。我们将从图数据结构的基础讲起,逐步深入到如何利用图算法识别欺诈行为,并介绍相关的工具和实践步骤。
什么是图数据结构? 🧩
上一节我们介绍了课程概述,本节中我们来看看图的基本构成。图是一种数据结构,由节点、边以及属性构成。
- 节点:代表实体,例如一个人、一个账户或一个设备。
- 边:代表节点之间的关系或连接。
- 属性:描述节点或边的特征信息,例如节点的名称、边的权重。
我们可以用《还珠格格》的角色关系来理解:
- 节点“乾隆皇帝”具有“名字=皇帝”、“星座=天秤座”等属性。
- 他与节点“小燕子”之间有一条边,属性为“关系=封为格格”。
- 他与节点“令妃”之间也有一条边,属性“爱的程度”可以用权重
weight=8来表示。
这种结构能够清晰地表达复杂的关系网络。
图算法在反欺诈中的意义 🎯
理解了图的基本结构后,我们来看看它在反欺诈场景中的具体意义。图算法主要能解决两类核心问题:核实身份和发现欺诈团伙。
以下是两种典型的反欺诈场景:
-
核实身份
假设金融机构存档的“皇后”信息是基于《还珠格格》版本。当一个自称“皇后”的人(实际是《如懿传》版本)来申请贷款时,系统需要有能力核查并判断其身份不符,从而进行拦截。这就是“核实身份”的应用。 -
团伙欺诈侦测
在复杂的社交或交易网络中,黑产分子往往以团伙形式作案。如果我们通过行业共享黑名单只知道一个可疑节点(例如“容嬷嬷”),图算法可以帮助我们找出与之关联紧密的整个社区(例如“皇后”及其党羽),从而实现团伙挖掘。
实践前的关键考量:你的图是连通的吗? 🔗
在将图算法应用到模型之前,有一个关键误区需要避免:检查你的数据是否构成了一个连通图。
图算法大致分为三类:
- 路径查找算法:解决最短路径等问题。
- 中心性算法:衡量网络中节点的重要性。
- 社区发现算法:用于侦测团伙欺诈,找出紧密关联的群体。
一个理想的、有助于发现欺诈团伙的图应该是充分连通的。这意味着社区内部连接紧密,同时社区之间也存在一些连接。这样,当一个黑名单节点出现时,算法才能有效地找到其关联的整个可疑群体。
反之,如果数据构成的图像“岛屿”一样彼此孤立(例如平均连接度很低),那么社区发现算法会将图分割成大量细碎的社区。这种情况下,正常群体和欺诈群体的图谱看起来可能没有区别,算法就无法进行有效区分。
初步检验方法:可以通过简单的统计(如计算节点的平均出度、入度)来初步判断你的数据是否具有形成连通图的潜力。
如何上手:工具与实现 🛠️
上一节我们讨论了数据连通性的重要性,本节中我们来看看如何用Python工具实现图算法。经过研究,Neo4j 是一个非常适合入门的图数据库。
以下是几种常用的工具及其特点:
-
Neo4j:一个流行的图数据库,可通过Web界面进行可视化,默认支持显示约3000个节点。它使用 Cypher 查询语言来操作图数据。
// Cypher 示例:创建节点和关系 CREATE (乾隆:皇帝 {name: '乾隆', 星座: '天秤座'}) CREATE (令妃:妃子 {name: '令妃'}) CREATE (乾隆)-[:宠爱 {weight: 8}]->(令妃)通常,我们用Python进行数据处理,再将结果导入Neo4j。Neo4j的扩展库(如
algo和apoc)提供了丰富的图算法。 -
NetworkX:一个纯Python的图论与复杂网络建模工具包,适合学术研究和中小型图的计算。
-
Gephi:一款开源的网络分析与可视化软件,角色与Neo4j类似,但专注于可视化,能处理较大规模的图。
-
GraphQL:一种由Facebook开源的数据查询语言,适用于查询图结构中的数据,可以灵活地获取节点、边及其属性。
对于初学者,从Neo4j开始是一个不错的选择,其社区活跃且有丰富的中文资料。
与反欺诈业务场景结合 💼
第三点,也是最关键的一点,是如何将图算法与具体的反欺诈业务场景深度结合。这通常贯穿于信贷的整个风控流程:
- 贷前与贷中:主要用于上文提到的“核实身份”和“团伙欺诈侦测”。
- 贷后:可用于寻找“失联客户”。当某个账户失联时,可以通过图算法找到其潜在的关系联系人,辅助催收工作。
要实现有效的图算法风控,核心在于构建一个信息量足够丰富的连通图。这要求数据维度不能仅限于账户ID,还应包含手机号、IP地址、设备号等多维度信息。理想情况下,建立行业级的黑名单共享联盟,将极大增强识别跨平台、跨场景欺诈团伙的能力。
总结 📝
本节课中我们一起学习了图算法在反欺诈中的应用。我们从图的基本概念出发,了解了它在核实身份和发现欺诈团伙中的价值。我们强调了在应用前检查数据连通性的重要性,并介绍了Neo4j、NetworkX等实用的工具。最后,我们探讨了如何将图算法嵌入到贷前、贷中、贷后的完整风控流程中,并指出构建多维度数据联盟是提升反欺诈效果的关键。

希望本教程能帮助你开启图算法在反欺诈领域的探索之旅。
011:Python在视效行业的应用

概述
在本节课中,我们将学习Python在影视特效(VFX)行业中的应用。我们将了解视效制作的基本流程、行业现状、Python在其中扮演的角色,以及新兴技术对视效行业的影响。
视效制作流程简介
上一节我们概述了课程内容,本节中我们来看看视效是如何制作的。一个特效镜头通常被分解为多个环节进行制作。
以下是视效制作的主要环节:
- 资产模型环节:创建现实中不存在的物体或角色,例如怪兽、飞船等数字资产。
- 动画环节:为创建好的数字资产添加动作,使其动起来。
- 合成环节:将实拍的绿幕画面、CG资产、动画等所有元素结合,处理画面不干净的部分,最终合成一个完整的镜头。
一部拥有上千个特效镜头的电影,其制作团队可能超过2000人。因此,视效行业可以被理解为一个劳动密集型行业。
视效行业中的软件与Python
了解了基本流程后,我们来看看行业中使用的主流工具。在视效行业,主流的专业制作软件包括Maya、Houdini和Nuke等。许多大型电影的背后制作都依赖于这些软件。
国外的大型公司,如迪士尼、工业光魔(ILM)和维塔数码(Weta Digital),通常拥有自己的开发团队,为其制作流程开发专用工具。这是目前国内视效公司相对欠缺的领域。
Python与这些软件和操作系统有良好的接口。例如,在Maya中可以直接调用其Python库进行编程,实现自动化任务和流程化应用。核心的调用方式通常如下:
# 示例:在Maya中通过Python创建一个立方体
import maya.cmds as cmds
cmds.polyCube()
国内视效现状与挑战
上一节我们介绍了行业工具,本节我们来关注国内视效行业的现状。国内视效常被观众戏称为“五毛特效”。这种现象主要由两方面原因造成。
以下是两个主要原因:
- 制作周期与预算:优秀的视效项目需要相对充足的制作时间和预算支持。
- 人才短缺:视效,尤其是计算机图形学,是艺术与技术的结合。艺术家离不开计算机技术的支持。目前国内既懂艺术又懂技术的人才较为稀缺。
因此,本次分享也希望能让更多Python开发者关注并了解视效行业,为提升国内视效质量贡献力量。
新技术在视效中的应用
传统制作方式面临挑战,而新技术带来了新的可能。深度学习与人工智能等技术已开始在视效行业应用。
以下是几个应用方向:
- 画面修复与擦除:自动识别并移除画面中不需要的物体(如穿帮的威亚、标记点),替换为背景。这个过程常被称为“擦威亚”或“Roto”。
- 图像风格迁移:改变场景的时间段或整体风格,例如将白天场景变为夜晚。
- 数字换脸:在影视制作中,可用于处理危险镜头的替身演员面部替换,或演员档期冲突等问题。通过AI技术可以大幅提升处理效率。
- 动作捕捉:实时捕捉演员的动作,并驱动数字角色模型。这在《猩球崛起》等电影中已有成熟应用。
通过编程和AI技术节约艺术家重复性劳动的时间,对于提升整体视效质量和行业效率有巨大帮助。
Python在视效行业中的角色
最后,我们来总结一下Python开发者在视效行业中可以扮演的具体角色。视效行业与IT行业有相似之处,如工作强度大、经常加班。同时,它是一个技术与艺术结合的领域,经验随着年限积累会越来越有价值。
Python在视效团队中的职能主要可分为以下几类:
- 工具开发(产品经理):规划和开发视效制作流程中所需的通用工具或插件,以提升制作效率。
- 流程管道开发(Pipeline TD):负责构建和梳理不同制作环节(如模型、动画、合成)之间的衔接流程。确保上一个环节的输出能自动兼容下一个环节的输入,并实现任务自动流转。
- 专业技术指导(Technical Director):针对特定环节(如模型、特效模拟)提供技术支持。例如,制作爆炸、水流等物理特效需要运用物理规律和数学原理(如线性代数)。公式
F = m * a(牛顿第二定律)是模拟许多物理现象的基础。 - 图形图像研发:这是最核心的技术岗位,专注于计算机图形学底层算法的研究与实现,直接处理图形图像相关的技术难题。

总结
本节课中,我们一起学习了Python在影视特效行业的广泛应用。我们从视效制作的基本流程入手,了解了行业现状与“五毛特效”背后的挑战,探讨了AI等新技术如何赋能视效制作,并最终明确了Python开发者在该行业中可以承担的工具开发、流程构建、技术支持和图形研发等关键角色。希望本次分享能为大家打开一扇通往技术与艺术结合领域的新大门。
012:以声音制造为例 🎵

在本教程中,我们将学习如何将Python编程融入高中技术课程,并以“声音制造”项目为例,展示如何通过编程连接数学、物理等学科知识,引导学生进行创造性的学习与实践。
课程背景与设计思路 🧭
上一节我们介绍了课程的整体框架,本节中我们来看看课程设计的具体背景与核心思路。
我们学校的课程设置不区分独立的信息技术与通用技术,而是将两者融合。课程采用项目制,学生跨年级选课,教师拥有较大的课程设计自主权。
课程设计的核心思路有以下几点:
- 融合主干学科知识:课程内容不仅限于技术,还融合了数学、物理、化学等学科的知识。
- 明确研究对象:以“声音制造”作为具体的研究对象,为学生提供明确的学习目标。
- 培养研究能力:在有限的空间内,引导学生应用现有工具解决实际问题,培养研究性学习能力。
- 衔接大学知识:尝试平滑中学知识与大学专业知识之间的过渡。
项目概述:声音制造 🔊
上一节我们了解了课程的设计思路,本节中我们将聚焦于“声音制造”这个核心项目。
学生需要完成的任务是动手制作一款可以演奏的电子乐器。为了实现这个目标,他们需要研究以下五个主要板块:
- 了解声音是什么。
- 分析声音。
- 制造声音。
- 处理声音。
- 传播声音。
在这个过程中,学生需要融合运用他们在各主干学科中学到的知识。
Python在课程中的核心角色 🐍
上一节我们明确了项目目标,本节中我们来看看Python编程如何作为核心工具贯穿整个学习过程。
课程开始时,学生会先复习相关的数学(三角函数)和物理(振动)知识。同时,他们会认识到计算机作为工具的两个基本限制:有限的空间和有限的计算次数。
Python的角色由此介入。以下是Python在项目中的具体功能与应用步骤:
1. 生成与可视化声波
首先,学生需要理解声音信号可以用最简单的正弦波表示。利用Python和matplotlib库,他们可以将公式转化为可视化的波形图,从而直观理解计算机处理的是离散数据而非连续信号。
核心公式与代码示例:
生成一个正弦波的公式为:
y = A * sin(2 * π * f * t)
其中,A是振幅,f是频率,t是时间。
使用Python绘制正弦波的简化代码如下:
import numpy as np
import matplotlib.pyplot as plt
# 设置参数
A = 1 # 振幅
f = 440 # 频率 (Hz),例如标准音A
t = np.linspace(0, 0.01, 500) # 时间数组
# 生成正弦波数据
y = A * np.sin(2 * np.pi * f * t)
# 绘图
plt.plot(t, y)
plt.title('正弦波')
plt.xlabel('时间 (s)')
plt.ylabel('振幅')
plt.show()
2. 合成复杂波形与信号处理
在能够生成单一正弦波后,学生需要尝试生成不同频率的正弦波并将其叠加,合成更复杂的声音。接着,他们会接触到如矩形波等其他波形公式,并尝试生成。
一个常见的实践问题是:生成的声音在开始和结束时,音响可能出现“过载”的爆音。引导学生观察波形图,他们发现信号的电平上升/下降过程过于陡峭。
解决方案:
这个问题可以引导学生运用高一数学的分段函数概念来构建数学模型,使信号的起始和结束变得平滑。例如,在信号开始阶段(Attack)和结束阶段(Release)加入线性或指数的渐变过程。
这个过程实际上模拟了音频处理中的经典ADSR包络(Attack, Decay, Sustain, Release),是电子乐器中常见的功能。
3. 构建交互系统与硬件联动
学生接下来会设计简单的图形界面,并利用网络通信(如UDP协议)或串口通信,将电脑(负责繁重的音频处理)与树莓派(Raspberry Pi)或Arduino等智能硬件(负责触发控制)协同工作。
核心思考:
这自然会引发对计算机网络核心概念的探讨。例如,制作实时电子乐器时,使用TCP还是UDP协议更合适?这促使学生思考不同协议的特性(可靠性 vs. 实时性)。
通信的数据格式可以采用简单的JSON结构,例如:{"command": "play", "note": "C4", "duration": 0.5}。
对中学STEM/STEAM教育的思考 💡
上一节我们详细探讨了Python的技术实现,本节中我们来分享一些关于课程设计背后的教育理念。
我认同MIT媒体实验室倡导的技术与艺术融合的方向。对于中学的STEM/STEAM教学,我认为:
以下是几条核心的教育思考:
- 内容优于形式:教育的核心应从主干学科的教育大纲出发,找到技术与知识的自然结合点,而非追逐热门的编程或机器人套件。
- 平行深入,交叉融合:将中学生正在学习的各主干学科知识与技术并行连接,在连接过程中必然找到交叉点,从而帮助学生构建网状的知识体系,而非线性的学科隔离。
- 促进教师间深度合作:技术教师应与数学、物理、美术等学科老师深度合作,将他们的教学想法通过技术手段实现。
- 清晰展现学科联系:在教学过程中,明确展示如何用数学知识(如分段函数)解决信号处理问题,强化知识间的关联。
- 引导模块化知识管理:引导学生像程序员一样,对他们所学的知识和技能进行模块化归类与管理,这有助于提升学习和工作效率。
总结 📚

本节课中我们一起学习了如何将Python作为核心工具,设计一个名为“声音制造”的高中技术课程项目。我们从课程背景与融合学科的设计思路出发,详细探讨了如何通过Python生成、处理声波,并连接智能硬件制作电子乐器。最后,我们分享了以内容为核心、促进学科深度交叉融合的STEM教育理念。希望这个案例能展示出编程不仅是技术工具,更是连接不同学科、激发学生创造性思维的桥梁。
001:Python3之路 🐍


在本节课中,我们将回顾Python的发展历程,重点探讨Python2的缺陷、Python3带来的重要改进,以及这门语言未来面临的挑战。我们将以简单直白的方式,梳理Python从过去到现在的关键变化。
Python2的历史回顾
上一节我们介绍了课程概述,本节中我们来看看Python2的发展历程。
Python 2.0 发布于2000年。
Python 2.2 和 2.3 分别发布于2001年和2004年,引入了诸如上下文管理器、迭代器和生成器等重要语法。
PEP 3000 这个重要的提案发布于2006年,它决议将开发不向下兼容的Python3。
Python 2.7 发布于2010年,这是Python2系列中使用最广泛的版本。
Python 2 将于2020年1月正式结束维护。
Python2的主要缺陷
了解了Python2的历史后,本节我们来看看它存在哪些主要问题。
Python2的缺陷主要包括:编码支持不足、语义存在分歧、内建系统设计不合理。
以下是关于编码问题的具体说明:
在Python2中,字符串 ‘ABCD’ 的含义是模糊的。它可能代表一个包含四个字符的文本字符串,也可能代表四个十六进制数的字节数据。这是因为Python2没有严格区分文本数据和二进制数据。
以下是关于语义分歧的具体说明:
例如,range 和 xrange 的功能相似但返回类型不同,dict.keys() 和 dict.iterkeys() 也存在类似区别。这种不一致性增加了学习负担。
以下是关于内建系统设计的具体说明:
考虑以下异常处理代码:
try:
raise SimpleOneException()
except SimpleOneException:
raise SimpleTwoException()
在Python2中,当处理 SimpleOneException 时又抛出了 SimpleTwoException,那么原始的 SimpleOneException 信息会丢失。这只是一个设计不合理的例子。
由于存在这些破坏性缺陷,Python社区最终决定进行不向下兼容的升级,即Python3。
Python3带来的核心改进
上一节我们看到了Python2的诸多问题,本节中我们来看看Python3是如何解决这些问题的。
Python3为我们带来了:统一的编码处理、更严格的语义、更合理的设计,以及能提升生产效率的新特性。
统一的编码
Python3严格区分了文本(str)和二进制数据(bytes),字符串 ‘ABCD’ 只能是文本。同时,对Unicode提供了更好的默认支持。
更严格的语义
例如,range 统一了Python2中 range 和 xrange 的行为。许多内置模块也被重组,以提供更清晰的接口。
更合理的设计
对于之前提到的异常处理问题,Python3引入了 __cause__ 属性。使用 raise SimpleTwoException() from e 语法,可以清晰地保留异常链,使得调试更加方便。
提升生产效率的新特性
Python3引入了许多提高开发效率的功能,例如类型标注、新的项目配置方式等。
Python3的新特性:类型标注
上一节我们提到了新特性,本节我们深入看看其中一项:类型标注。
Python是一门动态的强类型语言。但在大型项目中,动态类型可能导致代码难以理解和维护。
考虑以下Flask代码片段,猜测参数类型:
@app.route(‘/‘, methods=[‘GET’])
def index(name):
return ‘Hello {}’.format(name)
很难直接看出 name 应该是什么类型。
解决方案是类型标注(PEP 484)。 它在Python 3.5中引入,为函数参数和返回值添加可选的类型提示。
使用类型标注改进上面的代码:
from typing import Dict, Any
@app.route(‘/‘, methods=[‘GET’])
def index(name: str) -> str:
return ‘Hello {}’.format(name)
现在,代码清晰地表明 name 是字符串,函数也返回字符串。这大大提升了代码的可读性和可维护性,并方便了第三方静态类型检查工具(如mypy)的使用。
Python3的新特性:项目配置
除了类型标注,Python3也在项目管理和配置方面做出了改进。
传统上,Python项目使用 setup.py 文件来描述元数据和依赖。但这个文件通常复杂且需要编写大量样板代码。
PEP 518 引入了一种新的、更清晰的配置方式。它鼓励使用像 pyproject.toml 这样的结构化配置文件。
以下是一个 pyproject.toml 的示例片段:
[tool.poetry]
name = “my-project”
version = “0.1.0”
description = “A simple project”
[tool.poetry.dependencies]
python = “^3.7”
requests = “^2.25”
[build-system]
requires = [“poetry-core>=1.0.0”]
build-backend = “poetry.core.masonry.api”
相较于传统的 setup.py,这种格式更简洁、更易读、更专注于项目描述本身。像 poetry 这样的现代工具全面支持PEP 518,可以极大地简化依赖管理和项目发布流程。
Python的现状与未来挑战
Python3已经取得了巨大进步,但这是否意味着Python已经完善了呢?本节我们来探讨它当前面临的挑战。
答案是否定的。Python在工程化方面仍面临挑战,主要集中在性能和代码治理两方面。
性能挑战
在需要处理高并发、极高QPS的场景下,Python的性能可能成为瓶颈。与Go、Java等语言相比,Python的全局解释器锁(GIL)和动态解释执行使其在纯计算性能上不占优势。在业务量巨大时,这可能直接转化为更高的服务器成本。
框架与代码治理挑战
Python的灵活性是一把双刃剑。考虑以下使用元类(metaclass)的代码:
class DemoMeta(type):
def __new__(mcs, name, bases, attrs):
for key in attrs:
if ‘abc’ in key:
# 对方法进行包装
pass
return super().__new__(mcs, name, bases, attrs)
元类可以深度干预类的创建行为。如果团队成员未经充分评估就使用此类高级特性,可能导致代码难以理解和维护,即“动态类型一时爽,重构全家火葬场”。
大型工程需要“框架治理”,即通过规范和工具约束代码风格与可用特性。Python在这方面的原生支持较弱,更依赖于团队自建的基础设施和严格的Code Review。
尽管有这些挑战,Python因其优美、简洁和开发高效的特点,依然深受喜爱。社区也在积极改进,例如探讨移除GIL的方案。我们希望Python未来能在保持优势的同时,更好地满足大型工程的需求。
总结与问答环节
本节课中我们一起学习了Python从版本2到版本3的演进之路。我们回顾了Python2在编码、语义和设计上的缺陷,详细了解了Python3如何通过统一编码、严格语义和新特性(如类型标注、现代化项目配置)来解决这些问题。最后,我们也探讨了Python在性能和工程治理方面面临的持续挑战。
以下是现场问答环节的精选内容:
问:国内开发者如何为Python社区做贡献?
答:贡献途径多样。可以直接参与CPython或官方项目,从修复文档错别字(typo)或简单的Bug开始。也可以为你常用的第三方库(如某个RPC框架的Python客户端)贡献代码,完善功能。此外,组织或参与技术分享、像PyCon这样的会议也是宝贵的贡献。
问:为什么有人说Python 3.6.5是对新手最友好的版本?
答:这可能是因为Python 3.6是一个长期支持(LTS)版本,非常稳定,且包含了现代Python的核心特性(如f-string在3.6引入)。但对于初学者,任何3.5之后的版本差异都不大,建议直接学习较新的稳定版(如3.8或3.9)。
问:Python在饿了么有哪些应用?
答:饿了么早期核心系统用Python开发。目前,随着技术栈演进,部分核心链路转向Java。Python现在主要应用于一些中间件系统,如服务发现、注册中心等,并且团队内部仍在积极推动Python在新场景下的应用。

本节课到此结束。Python之路仍在继续,它的优雅与实用激励着我们不断探索和学习。
002:AWS的Python原生应用浅析


概述
在本节课中,我们将一起探讨Python语言与云计算平台AWS的结合应用。我们将了解Python如何作为“胶水语言”,高效地连接和利用AWS提供的丰富云服务,从而让开发者能够更专注于业务逻辑的实现,而非底层基础设施的管理。
Python与AWS的发展历程
上一节我们介绍了课程的整体内容,本节中我们来看看Python和AWS各自的发展背景。
Python诞生于1989年,至今已有30多年的历史。它从1.0、2.0版本发展到如今的3.8版本,经历了互联网和IT行业迅猛发展的时代。许多开发者从Python 2.6版本开始接触,尽管现在主流已转向Python 3.5及更高版本。
云计算的发展历史则相对较短。亚马逊AWS起源于公司内部解决大规模系统开发效率的需求。2000年左右,亚马逊开始构建微服务架构,并在2006年将内部使用的通用微服务对外开放,正式推出了AWS。如今,AWS已遍布全球22个地理区域,提供超过165项服务。
AWS与Python SDK:Boto3
了解了发展背景后,我们来看看Python开发者如何与AWS服务交互。这主要通过一个名为Boto3的官方Python SDK实现。
AWS的所有服务都提供标准的API接口,总计涉及数千个API。为了简化调用过程(如处理签名、认证等),AWS将这些API封装成了各种语言的SDK。Boto3就是官方的Python SDK。
以下是使用Boto3启动一个EC2实例的简化代码示例:
import boto3
ec2 = boto3.resource('ec2')
instance = ec2.create_instances(ImageId='ami-12345678', MinCount=1, MaxCount=1)
这段代码的核心是创建一个EC2资源对象并调用create_instances方法。Boto3库本身已经处理了底层的复杂逻辑,使得代码非常简洁。AWS命令行工具(AWSCLI)也是基于Boto3开发的,这体现了Python作为“胶水语言”的特性。
基础设施即代码(IaC)与AWS CDK
上一节我们介绍了如何使用SDK调用单个服务,但在实际项目中,我们通常需要管理由多个服务组成的完整架构。本节中我们来看看如何用代码定义和管理这些基础设施。
传统的做法是使用AWS CloudFormation服务,通过JSON或YAML模板来描述基础设施。例如,一个包含VPC、负载均衡器和容器集群的三层架构模板可能超过500行。虽然这实现了“基础设施即代码”,但模板编写复杂,且不易复用。
为此,AWS推出了Cloud Development Kit(CDK)。它允许开发者使用Python、TypeScript等通用编程语言,以面向对象的方式来定义基础设施。
以下是一段使用AWS CDK(Python)定义VPC的简化代码:
from aws_cdk import core
from aws_cdk import aws_ec2 as ec2
class MyVpcStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
vpc = ec2.Vpc(self, "MyVpc", max_azs=2)
使用CDK,开发者可以像写普通代码一样,利用继承、组合等特性来构建基础设施,大大提升了可读性和可复用性。依赖关系也通过对象传递来管理,更加符合程序员的思维习惯。
无服务器计算与AWS Lambda
在实现了基础设施的代码化管理后,我们的目标更进一步:能否只关注业务逻辑本身,而完全不用管理服务器?这就是无服务器架构的核心理念。
AWS Lambda便是这样的服务。你只需上传代码,Lambda会负责运行和扩展,你只需为代码执行的时间付费。
以下是一个最简单的Python Lambda函数,它响应HTTP请求并返回“Hello World”:
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': 'Hello from Lambda!'
}
这个函数由event事件触发。事件可以来自API Gateway的HTTP请求、S3的文件上传等。当并发请求激增时,Lambda会自动扩展,并行运行多个函数实例,开发者无需进行任何容量规划。
一个更实际的例子是,当用户上传图片到S3时,自动触发Lambda函数进行缩略图处理:
import boto3
from PIL import Image
import io
s3 = boto3.client('s3')
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# 从S3下载图片
file_byte_string = s3.get_object(Bucket=bucket, Key=key)['Body'].read()
image = Image.open(io.BytesIO(file_byte_string))
# 处理图片(例如调整大小)
image.thumbnail((100, 100))
# 保存回S3
buffer = io.BytesIO()
image.save(buffer, 'JPEG')
buffer.seek(0)
new_key = f"resized/{key}"
s3.put_object(Bucket=bucket, Key=new_key, Body=buffer)
return f'Image {key} was resized and saved as {new_key}'
这段代码纯粹是业务逻辑:下载、处理、上传。开发者完全不需要关心代码运行在什么操作系统、需要多少内存或CPU。
人工智能与机器学习服务
Python是人工智能和机器学习领域的主流语言。AWS为不同层次的AI/ML需求提供了相应的服务,并与Python深度集成。
1. 基础设施层
对于需要强大算力的模型训练,AWS提供了搭载最新GPU(如V100)的实例。通过Amazon SageMaker,你可以快速获得一个预装了TensorFlow、PyTorch等主流框架的深度学习环境。
# 在SageMaker Notebook实例中快速切换框架
import sys
!{sys.executable} -m pip install tensorflow
# 或者
!{sys.executable} -m pip install torch torchvision
2. 自动化机器学习
调参和模型选择是耗时的“人工”工作。Amazon SageMaker的自动模型调优(超参数优化)功能可以自动寻找最佳参数组合。由于云端的弹性,你可以并行发起数百次训练任务,大幅缩短优化时间。
3. 即用型AI服务
对于不想训练模型,只想在应用中集成AI能力的开发者,AWS提供了多种即用型AI服务(AI Services),可通过简单的API调用使用。
例如,使用Amazon Rekognition进行图像标签识别:
import boto3
client = boto3.client('rekognition')
response = client.detect_labels(
Image={'S3Object': {'Bucket': 'my-bucket', 'Name': 'photo.jpg'}}
)
for label in response['Labels']:
print(f"Label: {label['Name']}, Confidence: {label['Confidence']:.2f}")
这些服务涵盖了计算机视觉、自然语言处理、预测、推荐等多个领域,让开发者能以最低门槛为应用添加智能。
物联网(IoT)与边缘计算
Python在资源受限的物联网领域曾经不占优势,但随着边缘设备能力的增强,Python也开始发挥作用。AWS IoT服务提供了Python SDK,方便在树莓派等网关设备上开发。
AWS IoT Greengrass允许在本地边缘设备上运行Lambda函数,实现低延迟的数据处理和响应,即使在与云端断开连接时也能工作。训练好的机器学习模型也可以部署到边缘设备上进行实时推理。
总结
本节课中我们一起学习了Python在AWS云平台上的各种应用模式。
我们回顾了Python与AWS的发展,认识了通过Boto3 SDK调用云服务的基础方法。接着,我们探讨了用AWS CDK实现“基础设施即代码”,以及通过AWS Lambda构建无服务器应用,从而让开发者只专注于业务逻辑。在AI/ML领域,我们看到了从底层算力、自动化训练到即用型AI服务的完整金字塔。最后,我们了解了Python在物联网和边缘计算中的新角色。

正如课程开篇所言,Python就像高效的“胶水”,而AWS提供了坚实、丰富的“基石”。掌握如何使用Python粘合这些云服务,能让我们更快地将创意转化为现实。希望本课程能帮助你更好地利用Python和AWS的强大能力。
003:超越代码的成长


概述
在本节课中,我们将探讨软件开发者除了技术能力之外,同样重要的软技能。我们将从个人成长、团队协作以及与管理者相处等多个维度,学习如何成为一名更全面、更高效的开发者。
个人成长:从追求“最好”到成为“管家”
上一节我们介绍了课程概述,本节中我们来看看个人成长的心态转变。
在学校,我们很容易通过成绩判断自己是否“最好”。但在职场中,“最好”的定义变得模糊。许多人试图通过成为完美主义者来证明自己,例如花费大量时间优化每一行代码,追求极致的性能。然而,过度追求完美可能导致项目延误甚至被取消,因为客户真正需要的是可用的产品,而非完美的代码。
公式:完美主义 ≠ 效率
另一种错误方式是试图在同事中竞争,认为自己的技术或想法更优。但同事不是竞争对手,而是创意的源泉。与同事竞争无益于个人,更无益于团队和公司。
我们拥有的真正财富是管家职责——即管理好我们被赋予的资源:
- 能力:有人擅长编码,有人擅长组织或文档。我们需要善用各自的天赋。
- 经验:每个人的经历都是独一无二的。我的经历包含挤牛奶和在德国生活,而你的经历则塑造了独特的你。关键在于我们是否充分利用了自己的经验。
- 资源:包括财富、智力、人脉和机会。有效利用现有资源,远比羡慕他人拥有的更重要。
试图成为别人,是对自身独特性的浪费。我们应专注于利用好自己拥有的一切,并持续成长。
寻找内在驱动力与拥抱挑战
理解了个人定位后,我们来看看如何找到持续成长的动力。
面对众多选择时,一个有效的方法是反问自己:“什么是你‘无法不做’的事情?” 就像面包师在失败后会回去研读新食谱,开发者遇到难以解决的Bug时,即使暂时走开,内心仍会被问题吸引,渴望找到答案。找到这件让你着迷、无法放弃的事,就是你的内在驱动力。
成长往往源于困难,而非轻松。当我为了一个实际项目学习Python时,因为遇到了具体的困惑和需求,学习效率远高于漫无目的地阅读。挑战迫使我们深入思考和学习。
一位德国诗人的话很好地描述了开发者的生涯:“胜利并不诱惑此人。他通过被击败而成长,被日益强大的对手击败。” 我们的“对手”就是不断出现的技术难题。今年的问题在这里,五年后的问题可能在那里,但正是解决这些更复杂问题的过程,推动我们不断进步。
有些目标无法直接达成。例如,直接追求“受欢迎”往往适得其反。但如果我们专注于真诚待人、成为可靠的朋友,自然会获得友谊。间接途径有时比直接冲刺更有效。我们应专注于做真实的自己,目标会随之实现。
团队协作:我们并非孤岛
个人成长离不开环境,本节我们将目光转向团队协作。
没有人是一座孤岛。我们今日的成就,都或多或少依赖于他人的帮助。在技术领域尤其如此,开源社区、技术会议、同事和同学构成了强大的支持网络。
与同事共事时,需牢记:项目可能消亡,但关系长存。我曾花费四年开发一个最终被取消的C语言项目,代码从未被使用,但我从那时建立的职业联系至今仍在。技术人常专注于结果和算法,但项目有始有终,而人际关系却可能持续一生。如果我们为了证明自己正确而疏远了所有人,最终将一无所获。
因此,如何对待他人比如何对待代码更重要。确保离开时关系良好,因为未来很可能再次相遇。
公司的独特竞争力不在于可以购买的硬件或云服务,而在于人及其团队。更好的想法、互动与合作来自将同事视为资源,而非障碍。团队是为了共同抵达目的地。
欣赏式探询:聚焦优势,而非问题
在团队中,如何看待问题和同事至关重要。这里介绍一个深刻影响我思维的理念:欣赏式探询。
“欣赏”一词既有“重视、赏识”之意,也有“增值”之意。欣赏式探询就是通过提问,探寻人、组织及周围世界中最好的部分、优势以及充满机遇的领域。
传统管理思维习惯于寻找问题:“哪里出错了?需要修复什么?” 而欣赏式探询则转向寻找亮点:“什么在顺利进行?其中美好的部分是什么?” 如果我们有95%的客户满意,与其纠结于5%的不满,不如深入研究那95%满意的原因,然后做得更多、更好。
将这种思维应用于代码审查:当你打开一段代码并想“是哪个傻瓜写的?”时,请保持善意。寻找其中的优点,因为那个“傻瓜”可能就是六个月前的你自己。通过关注已有的“好”,并设法扩大它,我们能为团队创造更积极、更有建设性的环境。
与管理者有效合作
最后,我们来看看如何与管理者建立富有成效的关系。我曾担任过几年经理,也从员工角度深刻理解这种关系的复杂性。
一位好的管理者能提供以下价值:
- 结构:明确边界和必须完成的事项,为工作提供清晰框架。
- 保护:像雨伞一样,保护团队免受来自更高层的不切实际的想法干扰。
- 许可:有时,团队成员需要一个权威声音来认可他们的好想法,给予他们实施的空间。
- 培养:帮助团队成员规划成长路径,挑战新技能。
- 权威:你可以借助管理者的权威来推进工作(例如,“我和经理讨论过这个方案”),管理者也可以为你提供支持。
那么,管理者需要从我们这里得到什么呢?
- 追随者:我们需要愿意接受管理,即使我们未来也想成为管理者。
- 无需借口:管理者需要结果,而非迟到或未完成任务的借口。诚实沟通困难。
- 无需意外:如果项目可能延迟,应尽早告知,以便管理者协调应对,而非在最后期限才宣布。
- 坦诚的议程:公开你的职业兴趣和发展方向,帮助管理者更好地支持你。
- 现实的时间表:软件开发本质是探索,很难精确预估。提供尽可能诚实的时间评估和反馈。
- 诚实的工作量反馈:总是说“是”的“英雄”最终会筋疲力尽。沟通合理的工作负荷,如果公司需求持续超出团队能力,需要让管理者知道,以便做出调整(如招聘、调整策略),这比耗尽离职对所有人都有利。
关于动机的思考
关于如何激励团队或个人,我特别喜欢圣埃克苏佩里的一句话:“如果你想造一艘船,不要鼓动人们去收集木材,分配工作、发号施令。相反,请教会他们向往广阔无垠的大海。”
外在激励如头衔、金钱、机会有其作用,但最持久、最强大的动力源于内心。当人们内心渴望“驶向海洋”时,他们会自己找到木材、分工合作并完成任务。作为开发者,找到那件让你“心向往之”的事情,是保持热情和创造力的关键。
总结
本节课中我们一起学习了软件开发者至关重要的软技能。我们从个人成长的心态转变开始,探讨了从追求外在“最好”到善用内在资源的“管家”思维。我们学习了通过寻找“无法不做”之事来获得内在驱动力,并认识到挑战是成长的催化剂。
接着,我们聚焦于团队协作,明白了人际关系比单一项目成果更持久,应将同事视为共同成功的资源而非竞争对手。我们引入了欣赏式探询这一强大工具,学会聚焦于优势而非问题。
最后,我们探讨了如何与管理者建立双向、健康的合作关系,理解彼此的需求与价值,并通过诚实沟通实现共同目标。记住,持久的动力源于内心的渴望,而非外部的指令。

希望这些来自多年经验(包括错误决策)的分享,能帮助你在技术道路和职业发展中,不仅成为一名优秀的编码者,更成为一名卓越的合作者与终身成长者。
004:解放 Python 的表达力、性能和安全性


概述
在本节课中,我们将要学习如何通过语法扩展来增强 Python 的表达力,以及如何通过即时编译技术来提升 Python 的性能。我们将探讨语法扩展的原理、实现方式,并深入了解一个创新的 JIT 编译器架构。
语法和语义的扩展
上一节我们介绍了课程的整体目标,本节中我们来看看语法和语义扩展的具体内容。
语法和语义的扩展旨在扩展 Python 语言本身。在之前的讨论中,我们探讨了为何需要这样的设计。今天我们将侧重于如何使用这些扩展,并提供一个教程,展示有哪些可用的语法和语义扩展。
我们将演示一部分功能,例如模式匹配。模式匹配源于逻辑编程中的高级构造,在 Haskell 等语言中非常好用。它可以降低代码冗余。另一个例子是管道运算符,它类似于 Linux 的管道操作,可以将前一个操作的结果传递给后面的函数继续运算。我们将演示这些功能。
这种表达力的扩展是如何实现的?它保留了 Python 本身的可用性。你可以在任何时候安全地开启或关闭它,并且这是一种“免费的午餐”。
首先,需要理解的是,你所使用的语言决定了你的思维模型。在解决问题时,你拥有的语言决定了你如何思考这个问题。例如,在 C++、Haskell 和 APL 中求质数,它们的实现方式和思维模型截然不同。Haskell 和 APL 可以用一行代码实现,而 C++ 则需要更多代码。这表明语言从根本上改变了解决问题的思维方式。
在实际业务中,模式匹配非常有用。例如,根据运输工具的数据计算用户需要支付的价格。使用模式匹配可以直接给出直观的结果。代码清晰地表达了匹配规则:如果是一辆特定品牌和乘客数量的汽车,则输出对应的价格。这种对应关系比使用大量的 if-else 语句更加明确和直观。
在 Python 中,要实现同样的逻辑,你需要编写许多 if-else 语句,这并不直观。虽然代码作者可能觉得清楚,但其他人第一眼看到时无法立刻理解其意图。
语言中的语法问题决定了其真实的表达力。如果在一个语言中实现某个功能需要编写大量繁琐的代码,我们会下意识地抗拒这种写法,转而选择更简单的方法。因此,我们希望 Python 能够更加完善,能够容纳所有高级语法结构,以扩展我们的思维。
扩展系统设计
上一节我们了解了语法扩展的价值,本节中我们来看看扩展系统的具体设计。
我设计的系统使用 pragma 作为标志。例如,在文件开头写入 # pragma 可以开启语法扩展。但仅仅写入 # pragma 并不会启用任何扩展,你必须在开启扩展后,使用 #? 加扩展名来指定使用哪个扩展。
例如,开启管道运算符扩展后,map(_*2, range(10)) 中的下划线 _ 表示一个函数,map 的第一个参数是 _*2,它本身是一个函数。管道运算符原本在 Python 中不存在,但通过添加扩展,range(10) | _*2 就变成了函数调用。底下的代码也是语法扩展。
这可能看起来很神奇,但其实现是非常“卫生”的。它与现有框架的不同之处在于,有些框架会使用 inspect 模块来查找源代码位置,然后重新读取函数所在的原始代码。这种操作非常不可靠。例如,如果源文件不存在,Python 文件就无法运行。很多时候,我们希望将 Python 打包成 pyc 文件发布,这就需要一种与 Python 完全兼容的机制。
这个系统的第六个特点是,在任何情况下,如果你不使用 Python 原本就有的功能,在使用这个系统后,这些功能仍然保留。它只是一个附加的扩展。
工作原理:导入机制
上一节我们介绍了扩展系统的设计理念,本节中我们深入探讨其工作原理,即 Python 的导入机制。
它的实现依赖于 Python 的 import 机制。Python 的导入过程是:首先从 sys.meta_path 中查找许多查找器,然后根据你的 import 语句找到对应的加载器。这个加载器是内置的或用户实现的。加载器决定了如何加载模块。
我们的做法是劫持 Python 源代码的加载器。在它读取源代码之后,我们重载 load 方法。例如,在加载代码前,我们先处理未开启的扩展。这样可以保证任何行为只要 Python 原本支持,我们也支持。
具体做法是:劫持加载器,读取源代码,进行处理,然后返回。简单来说,它就像一个新语言,只是将 Python 编译到 Python。它有一个用户易于扩展的编译器,并且可以编译成 Python 字节码发布,完全兼容 Python 生态。
语法扩展的实现基础
上一节我们了解了扩展系统如何利用导入机制工作,本节中我们来看看实现语法扩展的基础:宏和模式匹配。
首先,它的实现依赖于真正的语法宏。语法宏是一个从语法树到语法树的函数。Python 本身提供了宏机制,例如 import ast 后,ast.parse 可以生成语法树列表。虽然用起来不是很好用,但它确实存在,名为 MacroPy,大家可以查询一下。
接下来我们讲模式匹配。模式匹配有很多用法。我们将演示一下。
(演示部分说明:由于现场设备问题,演示未能顺利进行。理论上,代码中 #? pattern 表示开启模式匹配扩展。match [1,2,3]: 是一个标志符。在开启扩展的区域,如果出现 match 标志符,就意味着要将序列进行匹配。如果 match 多个参数,下面的 case 会进行对应。例如 case [1, a] 和 case [2, b] 会自动解构并打印结果。)
它的性能比纯 Python 高数量级,这里是 20 多倍。项目链接中可以查到这个数据。
占位符与函数式编程
上一节我们介绍了模式匹配,本节中我们来看看另一个有用的语法扩展:占位符。
placeholder 来自 Scala。例如,f(_ + 1) 中的下划线表示一个函数参数,它变成了 lambda x: x + 1。这样可以少写一些代码。如果是两个参数,在不同参数位上用下划线,它们代表不同的参数。可以用 _1 或 _2 来表示第一个和第二个参数。
例如,reduce(_0 + _1, s) 就是对列表 s 进行求和。还有 this_placeholder,它和 C++ 的 this 有点像。this_placeholder 把整个函数调用看成了一个整体,其第一个参数是 _。这个占位符的好处是,函数 f 本身也可以作为占位符。例如,交换位置后,它表示传入一个函数,然后调用这个函数 f。
这些东西都是可配置的。例如,你可以将下划线换成 it,可以自定义语法。这提供了很大的灵活性。
需要强调的是,这不是函数式编程。很多人看到模式匹配和 Lambda 就认为是函数式编程,认为函数式编程很慢,这些观点都是错误的。首先,函数式编程并不慢。其次,这些扩展只是 Python 的语法糖,并不是函数式编程。很多人想表示自己懂一些概念,就说这是函数式,其实并不是。
如何实现和扩展运算符重载
上一节我们澄清了关于函数式编程的误解,本节中我们来看看如何具体实现和扩展语法,例如运算符重载。
实现一个叫做 scoped_operator 的扩展,它表示可以重载开启扩展范围内的所有二元运算符。例如,可以支持列表的减法:[1,2,3] - [1,2] 得到 [3]。这种重载可以通过扩展来实现。
实现起来很简单。首先从 extension 导入 Extension,然后从 ast_compat 导入 AST(为了支持 Python 3.5 以上所有版本)。定义一个类 ScopedOperatorExtension。它的 identifier 就是 scoped_operator。在 visit 方法中,会接收到操作符字符串和函数名。然后检查该操作符是否在预定义的映射中。如果在,就将其转换为一个二元函数调用,例如把 a + b 变成 my_func(a, b)。这样就实现了运算符重载。
visit 方法是访问语法树并可能返回新树的过程。它根据节点类型进行分发。你只需要在访问器类中实现 visit_BinOp 等方法即可。重写 ast 就是返回一个新的语法树。
这个映射是从 Python 的 ast 模块中直接获取的二元运算符列表。注意,比较运算符(如 >、<)不是二元运算符,不能这样重载。
JIT 编译器:性能优化
上一节我们探讨了语法扩展的实现,本节中我们进入今天另一个核心主题:即时编译器,这是我最自豪的工作。
这个 JIT 编译器是前所未有的。它的工作流程是:从 Python 字节码开始,经过多个编译阶段,最后变成后端代码,例如 JVM 字节码或 LLVM IR。这里面有很多难题:
- 为什么要从字节码开始编译?
- Python 虚拟机是基于栈的,如何做语义优化?
- 后端和前端有什么区别?为什么我们偏好某些后端?
- 重点是如何将栈机语义转换到寄存器机?
- 如何进行分析(如控制流分析、指针分析)?
- 如何生成 Phi 节点来消除栈语义?
- SSA 形式的 JIT 基础架构。
这个架构要感谢我的导师,它能够实现 Python 代码的热点优化,效果非常好。
为什么从字节码开始着手?
因为在运行时,你拿不到源代码。如果你依赖源代码,就会违背 Python 的兼容性。例如,inspect.getsource 会读取文件,这依赖于源码存在。如果我们以 .pyc 文件的形式发布 Python 包,这个功能就不可用了。我们希望在任何时候,即使在运行时,也能无缝启用 JIT,没有任何兼容性开销和副作用。
此外,pycodegen 这个库非常好用,它可以把 Python 字节码变成字节码对象,这样我们就不需要自己写解析器了。
栈机长什么样?
例如一个函数,它的字节码是:LOAD_CONST 0, STORE_FAST z, LOAD_FAST x, LOAD_CONST 2, COMPARE_OP... 解释器模拟栈的操作:压入常数 0,存储到局部变量 z,加载 x 和常数 2 到栈上,进行比较... 这种基于栈的操作效率不高,所以我们要优化它。
如何优化?首先要进行分析。
Python 字节码有 100 多个指令,一个人很难处理这么多。我们采用核心语言的思想:一个精简的核心指令集意义重大。我们将 Python 的指令集减少到 15 个以内,这是第一步。它把 Python 的栈机指令变成一个栈机和寄存器混合的指令集。
为什么不直接翻译到寄存器机?因为太难了。考虑一个例子:有多个 PUSH 指令后跟一个条件跳转。在跳转的目标处,有一个 POP 指令。你很难确定这个 POP 出来的值对应之前哪个 PUSH,特别是当从不同地方跳转过来时,栈上可能还有剩余元素。Python 还有一个 JUMP_IF_TRUE_OR_POP 指令,它优化起来相当麻烦。
如果不翻译,而是在编译时手动模拟栈(例如用一个列表来代表栈),这样是可以的。但结果是无法优化这个栈的模拟,导致循环非常慢。主要问题是从不同基本块跳转时,无法确定寄存器的值,并且 PUSH 操作无法优化。
后端选择
LLVM 后端是零优化。为了兼容 Python,它在每一步都进行引用计数操作。虽然引用计数本身感觉不慢,但它阻止了 C++ 进行深度优化。编译到 C 代码最多只是去掉了解释开销,这不算真正的 JIT,甚至可能比 Python 解释器还慢,因为 Python 解释器本身有很多优化。
为了让代码更快,我们必须进行分析以消除栈语义。如果我们用 C 语言后端,可以避免写这些分析吗?实际上,C 语言后端是“洪水猛兽”,启动时间超过 10 秒,调试一次间隔 30 秒,体验很差。
我们的方法是用“三地址码”,没有 goto,用程序分区来实现 goto,把不同的基本块编译成一个 while 循环。这样 C++ 编译器可以进行优化。最终效果是生成了高效的本地代码。
JIT 函数机制与性能
上一节我们分析了 JIT 编译的挑战与方案,本节中我们来看看 JIT 的具体函数机制和最终性能。
JIT 的函数机制很重要:首先创建一个 JIT 函数。它通过一个方法查找函数来查找不同的方法(基于类型的单态化)。函数参数最初没有类型。当一个调用发生时,会记录这个调用。调用记录器会累计次数,例如每 30 次就会唤醒 JIT 编译器。JIT 编译器查看调用记录,检查里面有哪些具体类型的方法,然后将其添加到函数中,编译出一个新的函数指针。接着更新所有相关的调用点,使用新生成的方法查找函数。我们不是动态派发,而是动态生成方法查找函数,这非常高效。方法查找函数会重新编译,链接到正确的具体实现上。
性能结果
性能比较显示,JIT 效果不错。图表左边是第一次运行(解释执行),右边是第二次运行(JIT 编译后)。第二次运行可以看到显著的性能提升。对于数值运算,可能会有几十倍的加速。这意味着 Python 的性能可以通过 JIT 获得数量级的提升。
目前的实现还缺少一些构造,如闭包,但未来都会实现。基本上所有代码都可以获得 2 倍以上的加速,数值运算则可能有几十倍的加速。
总结
本节课中我们一起学习了如何通过语法扩展来解放 Python 的表达力,以及如何通过创新的 JIT 编译器架构来大幅提升 Python 的性能。
我们探讨了:
- 语法和语义扩展的必要性与实现方式,包括模式匹配和管道运算符。
- 扩展系统的设计,它如何利用 Python 导入机制实现无缝集成。
- 语法扩展的具体实现基础,如宏和占位符。
- 一个高性能 JIT 编译器的设计挑战与解决方案,包括从栈机到寄存器机的转换、各种程序分析,以及最终的热点代码编译机制。

这些技术旨在让 Python 更强大、更快速,同时保持其易用性和兼容性。
005:静态类型的 Python


概述
在本节课中,我们将学习 Python 类型系统的基本概念,了解如何为 Python 代码添加静态类型检查,并探讨其背后的理论基础。我们将从类型与类型系统的区别讲起,逐步深入到 mypy 工具的使用和代数数据类型的概念。
第一部分:类型系统基础概念
在开始之前,我们先分享一个案例。三天前,一个群友遇到了代码问题。我们猜测是变量类型出了问题,但排查过程很麻烦。后来发现,问题出在 filter 函数返回的是一个惰性的 filter 对象,而 json.dumps 不支持序列化这种对象。如果当时使用了 mypy 这类静态分析器,或者 Python 默认带有类型检查,这类问题就能在编码阶段被轻易发现。
类型与类型系统的区别
我常听人说“Python 没有类型系统”,但对方会反驳“Python 有类型”。这里需要澄清:Python 有类型,但没有类型系统。
在 CPython 中,类型是运行时的概念。每个 Python 对象在 C 结构体头部都有一个 PyObject 对象头,其中包含一个指向类型对象的指针。这个类型对象就是 Python 在运行时识别对象类型的依据。
然而,类型系统是一种编译期的检查规则。它通过一套规则来保证代码的安全性,其基本目的是防止程序在运行时发生执行错误。类型系统是形式化方法的一种,属于编程语言理论中发展较为完善的部分。
类型系统的作用
类型系统主要有三个作用:
- 错误检查与安全性:最基本的作用是在代码运行前发现潜在的类型错误,提高程序的可靠性。
- 作为文档:带有类型标注的代码本身就是一种文档。通过函数签名,我们可以快速理解函数的功能。例如,一个签名为
(List[T], int) -> T的函数,很可能是一个索引操作。 - 利于编译优化:静态类型信息可以帮助编译器或解释器进行优化。动态语言由于类型不确定,往往需要在运行时进行哈希表查找和类型检查,这会损失性能。
类型系统的分类
上一节我们介绍了类型系统的作用,本节我们来看看它的几种分类。
- 渐进式类型系统:这种系统允许静态类型和动态类型共存,非常适合像 Python 这样的动态语言进行逐步的类型化改造。开发者可以逐步为遗留代码添加类型标注,而不必一次性重写全部代码。
- 声明式 vs 结构化类型系统:
- 声明式(名义式):通过类型的名称来进行类型检查和推导。
mypy采用的就是这种方式。 - 结构化:通过检查对象的“形状”(即拥有的属性和方法)来检查类型。TypeScript 的类型系统就是结构化的。这类似于 Python 的“鸭子类型”——如果一个对象看起来像鸭子,叫起来像鸭子,那么它就是鸭子。
- 声明式(名义式):通过类型的名称来进行类型检查和推导。
第二部分:Python 类型标注实战
了解了基本概念后,我们进入实战环节,看看如何为 Python 代码添加类型标注并进行检查。
Python 的标准支持
mypy 的实现依赖于 Python 的官方标准。
- PEP 3107 - 函数注解:在 Python 2 时代,函数参数和返回值的元信息没有标准,导致工具生态混乱。PEP 3107 统一了函数注解的语法,为类型提示提供了标准的语法载体。虽然这些注解在运行时可以获取,但 Python 本身不会进行任何类型检查,以保证兼容性。静态类型检查器(如
mypy)或 IDE 可以利用这些注解进行分析。 - PEP 526 - 变量注解语法:在 PEP 526 之前,人们通过注释(如
# type: int)来标注变量类型。这种方法不是官方语法,难以与普通注释区分,且解析困难。PEP 526 引入了专门的变量注解语法(如x: int = 5),解决了这些问题。
使用 Mypy 进行类型检查
以下是使用 mypy 的基本步骤和常见用法。
安装与基本使用
通过 pip 安装 mypy:pip install mypy。安装后,使用 mypy your_program.py 命令即可检查代码。通过 --python-version 2.7 参数可以检查 Python 2 的代码。
为函数添加类型标注
我们从一个普通函数开始,它实现字符串重复连接的功能。
def hello_name(name, repeat):
return 'hello ' + name * repeat
print(hello_name(3, 3)) # 运行时可能出错
print(hello_name(b'test', 3)) # 运行时可能出错
如果传入数字或字节数组,运行时可能会报错。使用 mypy 可以在运行前发现这些问题。我们需要为函数添加类型标注。
def hello_name(name: str, repeat: int) -> str:
return 'hello ' + name * repeat
现在 mypy 就能检查出 hello_name(3, 3) 的类型错误。
使用 Any 类型
如果希望某个参数可以接受任意类型,可以使用 Any。这在重构遗留代码时作为临时解决方案很有用。
from typing import Any
def func(param: Any) -> Any:
return param
注意:object 和 Any 不同。标注为 object 的变量只能调用 object 类的方法;而标注为 Any 则完全绕过类型检查。
默认参数
默认参数直接放在类型注解之后即可。
def greet(name: str = "World") -> str:
return f"Hello {name}"
容器类型标注
对于列表、字典等容器,需要使用 typing 模块中的泛型。
from typing import List, Dict, Tuple
def process_items(items: List[str]) -> None:
for item in items:
print(item)
def get_coordinates() -> Tuple[float, float]:
return (1.0, 2.0)
def get_student_grades() -> Dict[str, int]:
return {"Alice": 90, "Bob": 85}
可迭代对象与泛型
如果一个函数能接受 List 或 Tuple,使用 List[int] 标注会过于严格。这时可以使用更抽象的可迭代类型 Iterable。
from typing import Iterable, List
def sum_numbers(numbers: Iterable[int]) -> int:
total = 0
for n in numbers:
total += n
return total
联合类型 Union
Union 允许一个变量是多种类型之一,这比 Any 的约束更强。
from typing import Union
def handle_value(value: Union[int, str]) -> None:
if isinstance(value, int):
print(f"Got integer: {value}")
else:
print(f"Got string: {value}")
可选类型 Optional
mypy 默认变量不可为空(None)。Optional[int] 等价于 Union[int, None],表示变量可以是 int 或 None。
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
if user_id > 0:
return "UserFound"
else:
return None
局部类型推断
mypy 能够进行局部类型推断。例如下面的函数,mypy 能推断出 output 最终是 List[float] 类型,并与返回值标注 List[float] 进行比对检查。
from typing import Iterable, List
def make_list(iterable: Iterable[float]) -> List[float]:
output: List[float] = []
for item in iterable:
output.append(item)
return output
返回 None 与 NoReturn
-> None: 表示函数返回None值。-> NoReturn: 表示函数永远不会正常返回(例如,总是抛出异常或无限循环)。NoReturn类型可以被赋值给任何其他类型。
可调用对象
使用 Callable 标注函数或可调用对象。
from typing import Callable
def apply_func(func: Callable[[int, int], int], x: int, y: int) -> int:
return func(x, y)
常量与类型别名
Final: 标注一个变量为常量,不可重新赋值。- 类型别名:通过赋值创建,使复杂类型更易读。
from typing import Final, List, Tuple
MAX_SIZE: Final[int] = 100
# 类型别名
Vector = List[float]
Matrix = List[Vector]
def scale_vector(v: Vector, factor: float) -> Vector:
return [x * factor for x in v]
类成员注解
为类的属性添加类型标注。注意,直接标注 x: int 表示实例属性,且该标注被视为常量值。若要标注类变量,需使用 ClassVar。
from typing import ClassVar
class Point:
# 实例属性
x: int
y: int
# 类属性
origin: ClassVar[Tuple[int, int]] = (0, 0)
def __init__(self, x: int, y: int) -> None: # 注意 __init__ 返回 None
self.x = x
self.y = y
第三部分:代数数据类型——Mypy 的理论支撑
前面我们学习了如何使用 mypy,本节我们深入一点,看看其类型系统设计的理论基础——代数数据类型。
代数数据类型像研究代数一样研究类型之间的关系。我们可以将类型视为值的集合,类型的大小就是这个集合中元素的数量。
bool类型只有True和False两个值,所以其大小为 2。- 空元组
()和None都只有一个值,所以大小为 1。 typing模块中的NoReturn类型没有值,大小为 0。
积类型
像元组 (bool, bool) 这样的类型被称为积类型。它的可能取值有 (True, True), (True, False), (False, True), (False, False) 四种,其大小为组件类型大小的乘积,即 2 * 2 = 4。
和类型
Union[bool, int] 这样的类型被称为和类型。它的大小是组件类型大小的和。C 语言中的 union 不是真正的和类型,因为它没有标签来区分当前是哪种类型。而 Python 对象头中的类型指针可以充当这个标签。
子类型与 Top/Bottom 类型
- 子类型:如果类型 A 是类型 B 的子集,那么 A 是 B 的子类型。子类型具有自反性(A 是 A 的子类型)和传递性(如果 A 是 B 的子类型,B 是 C 的子类型,则 A 是 C 的子类型)。
- Top 类型:包含所有可能值的类型。在
mypy中,Any近似扮演这个角色,所有类型都是Any的子类型。 - Bottom 类型:不包含任何值的类型。
NoReturn就是这样一个类型。由于它没有实例,在逻辑上它可以被提升为任何其他类型。
None 的问题与 Optional
在 CPython 中,所有对象都可以是 None,这相当于所有类型都是 Optional[T]。这在不需要 None 的地方带来了问题。因此,显式使用 Optional[T] 来标注可能为 None 的类型是更严谨的做法。
Go 语言错误处理的类比
Go 语言使用多返回值 (value, error) 进行错误处理。这本质上是将积类型 Tuple[T, error] 当作和类型 Union[T, error] 来用,被一些研究者认为是有问题的设计。
第四部分:拓展思维(非正式内容)
最后一部分是拓展思维,内容不保证完全正确,仅供开阔思路。
更激进的类型推导
如果想让 Python 变得更像静态语言,可以考虑全局类型推导。例如,在 ML 系语言(如 F#)中,函数 def add(x, y): return x + y 可以被自动推导为具有两个相同泛型参数并返回相同类型的泛型函数。但由于 Python 缺乏类似 typeclass 或接口的机制,很难推导出“可加”这样的约束,推导结果会非常繁琐。
总结
本节课我们一起学习了 Python 类型系统的核心知识。我们从区分类型与类型系统开始,明确了类型系统是一种编译期的安全保障机制。接着,我们探讨了 Python 通过 PEP 3107 和 PEP 526 为类型提示提供的标准支持。
在实战部分,我们详细介绍了如何使用 mypy 工具,包括为函数、变量、容器、类成员添加类型标注,并理解了 Any, Union, Optional, Callable 等关键概念。
最后,我们从理论层面了解了 代数数据类型,将类型看作集合,用积类型、和类型、子类型等概念分析了 mypy 类型系统的设计逻辑。

希望本教程能帮助你开始在 Python 项目中使用静态类型检查,编写出更健壮、更易维护的代码。
006:基于 Flask 的 REST API 开发实践




在本教程中,我们将学习如何使用 Flask 框架开发 REST API。我们将从基础概念讲起,逐步深入到设计原则、工具选择以及使用原生 Flask 实现 API 的实践。本教程旨在为初学者提供一个清晰、实用的学习路径。
概述
本节课我们将要学习 Flask 框架在 REST API 开发中的应用。内容涵盖 Flask 简介、REST API 基础概念、相关术语梳理、设计原则探讨,以及如何利用 Flask 原生功能搭配工具链来构建 API。
第一部分:基础概念速览
如果你对 Flask 和 Web API 还不熟悉,这部分内容将为你提供一个快速的入门。
Flask 简介
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它的核心哲学是“微”,意味着它是一个可以从小开始,按需扩展的框架。你不需要一开始就引入所有功能,而是根据项目需求逐步添加。这种灵活性使其非常适合快速开发原型或构建 Web API。
安装 Flask 非常简单,使用 pip 命令即可:
pip install flask
入门 Flask 开发
一个最简单的 Flask 应用如下所示:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return '<h1>Hello, World!</h1>'
if __name__ == '__main__':
app.run()
这段代码创建了一个 Flask 应用实例,并定义了一个处理根 URL (/) 的视图函数。当用户访问该地址时,函数返回的 HTML 字符串会在浏览器中显示。运行这个程序,你就创建了一个基础的 Web 应用程序。
什么是 Web API
Web API(Application Programming Interface)是一种允许不同软件应用相互通信的接口。与直接面向用户、返回丰富交互界面(HTML)的 Web 程序不同,Web API 面向机器或其他程序,通常只返回结构化的数据(如 JSON)。这些数据被客户端(如 Web 前端、移动应用)获取后,再加工成用户界面。
入门 Web API 开发
使用 Flask 实现一个最简单的 Web API 与上面的 Web 程序非常相似,主要区别在于返回的数据格式:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/hello')
def hello_api():
return jsonify(message='Hello, API!')
if __name__ == '__main__':
app.run()
这里,视图函数返回一个由 jsonify() 处理过的字典,其响应内容类型(Content-Type)自动设置为 application/json,表明这是一个 JSON 格式的响应。
以上是对基础内容的介绍。接下来,我们将进入更深入的主题。
第二部分:术语梳理与设计思考
清晰的概念是理解复杂主题的基础。在深入实践之前,我们先对一些关键术语进行梳理。
关键术语辨析
在 Web API 开发领域,许多术语容易混淆。进行术语梳理有助于降低理解负担,避免定义混乱。
以下是几个需要厘清的术语及其常见用法:
- API / 接口:广义上指软件组件间的交互契约。在 Web 开发上下文中,通常特指 Web API。应避免与软件库的“接口”混淆。
- REST API / RESTful API:指遵循 REST 架构风格的 API。在实践中,该术语的外延已被扩大,许多使用 HTTP 传输 JSON/XML 的 API 都被称为 REST API,尽管它们可能并未完全遵循 REST 的所有约束。
- Web 服务:一个非常宽泛的术语,可以指任何通过网络为机器提供数据的服务(包括 Web API)。也存在一个由 W3C 制定的狭义“Web 服务”标准,容易造成混淆。
- 资源 (Resource):在 REST 语境下,指 API 暴露的数据实体(如用户、文章)。
- 端点 (Endpoint):通常可与 URL 或 URI 互换使用,指访问特定资源的地址。
- 请求/响应处理:包含序列化(将数据转换为传输格式,如 JSON)、反序列化(将接收到的格式转换回数据)、验证、格式化等多个过程。
REST 架构风格探讨
REST 是一种软件架构风格,提出了许多优秀的设计约束。然而,它并非一个严格的标准。在现实中,完全符合 REST 所有约束的 API 非常少。
我们应该参考 REST 的优秀实践,但不必教条式地遵守。设计的核心应从 API 自身的特点和需求出发,追求易于理解和维护,而非刻板符合某个风格。
例如:
- 资源命名:REST 建议使用名词(如
/users),但像/search(动词)这样的端点因其直观性而被广泛接受。 - 版本管理:REST 建议通过 HTTP 头(如
Accept)管理版本。但在实践中,将版本号嵌入 URL(如/api/v1/users)更为直观和流行。
核心思想是:一个 60% 符合 REST 但易于理解的 API,优于一个 100% 符合 REST 但难以使用的 API。
第三部分:Flask 生态与工具选择
上一节我们探讨了设计原则,本节中我们来看看在 Flask 生态中有什么工具可供选择。
目前流行的 Flask REST API 扩展情况各异,有的已不推荐使用,有的维护状态不佳。因此,直接推荐某个“大而全”的扩展存在风险。
这恰恰回归了选择 Flask 的初衷:用灵活性换取控制权。既然没有完美的“一站式”解决方案,我们可以选择自己搭配一套由多个优秀、专注的轻量级工具组成的工具链。
对于资源、端点、错误处理等基础功能,可以尝试直接用原生 Flask 实现。只在处理验证、序列化等复杂环节时,才引入专门的库。
第四部分:使用原生 Flask 实现 API 的实践
下面,我们将简要介绍如何使用 Flask 的原生功能来构建一个 API。请注意,这里的代码示例旨在展示思路和路径,而非需要记忆的模板。
1. 定义数据与响应
假设我们要开放一个“问候语”API。首先,模拟一些数据并创建响应辅助函数:
greetings = {
'en': 'Hello',
'es': 'Hola',
'fr': 'Bonjour'
}
from flask import jsonify
def json_response(data):
"""将字典数据转换为JSON响应"""
response = jsonify(data)
response.headers['Content-Type'] = 'application/json'
return response
从 Flask 1.1 开始,视图函数直接返回字典也会被自动转换为 JSON 响应。
2. 实现资源端点
可以使用普通的视图函数,也可以使用 Flask 的 MethodView 类来以类为单位组织资源:
from flask.views import MethodView
class GreetingAPI(MethodView):
def get(self, lang_code):
greeting = greetings.get(lang_code)
if greeting is None:
return json_response({'error': 'Language not found'}), 404
return json_response({'language': lang_code, 'greeting': greeting})
def post(self):
# 处理创建新问候语的逻辑
pass
# 注册路由
app.add_url_rule('/api/greeting/<lang_code>', view_func=GreetingAPI.as_view('greeting_api'))
MethodView 允许你将不同的 HTTP 方法(GET, POST 等)映射到类的不同方法上。
3. 使用蓝本管理 API 版本
使用 Flask 的蓝本(Blueprint)可以很好地组织代码并定义 API 版本前缀:
from flask import Blueprint
api_v1 = Blueprint('api_v1', __name__, url_prefix='/api/v1')
@api_v1.route('/greeting/<lang_code>')
def get_greeting_v1(lang_code):
# ... v1版本的逻辑
pass
app.register_blueprint(api_v1)
4. 错误处理
可以自定义错误处理器来返回结构化的错误响应:
from flask import jsonify
@app.errorhandler(404)
def api_not_found(error):
return jsonify({'error': 'Not found', 'message': 'The requested resource was not found.'}), 404
@app.errorhandler(500)
def internal_server_error(error):
return jsonify({'error': 'Internal Server Error', 'message': 'An unexpected error occurred.'}), 500
5. 数据模式与序列化
对于复杂的数据输出,可以定义模式函数来控制返回哪些字段,并添加描述性字段(如指向自身的链接 self_link):
def greeting_schema(greeting_dict, lang_code):
"""定义问候语资源的输出模式"""
return {
'self_link': f'/api/greeting/{lang_code}',
'kind': 'Greeting',
'language': lang_code,
'greeting': greeting_dict.get(lang_code),
'etag': 'some_hash_here' # 示例字段
}
6. 请求验证与认证
对于请求数据验证,手动处理比较繁琐,通常会引入像 marshmallow 或 pydantic 这样的库。对于身份认证,OAuth 2.0 是常用标准。根据 API 的开放程度(内部使用或公开),选择合适的认证模式(如密码模式或授权码模式)。
一个使用原生 Flask 实现简单令牌认证的装饰器示例如下:
from functools import wraps
from flask import request, jsonify
def token_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
token = None
# 从 Authorization 请求头获取令牌
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
try:
token_type, token = auth_header.split()
except ValueError:
return jsonify({'error': 'Invalid authorization header'}), 401
if not token:
return jsonify({'error': 'Token is missing'}), 401
# 这里应添加验证 token 有效性的逻辑
if token != 'valid_token_example':
return jsonify({'error': 'Token is invalid'}), 401
return f(*args, **kwargs)
return decorated_function
@app.route('/api/protected')
@token_required
def protected_resource():
return jsonify({'data': 'This is protected data'})
记住,代码本身并不最重要,重要的是学习路径:先理解 Web API 的设计原则,尝试用 Flask 原生功能实现,遇到瓶颈时再寻找合适的工具。
第五部分:其他选择与总结
除了使用 Flask,构建 Python Web API 还有很多其他优秀的框架选择,例如 FastAPI、Django REST framework、Falcon 等。如果你对性能或开发便利性有更高要求,可以探索这些框架。
本节课中,我们一起学习了:
- Flask 和 Web API 的基本概念。
- 对 Web API 开发中易混淆的术语进行了梳理。
- 探讨了 REST 架构风格,并强调设计应服务于理解和需求。
- 分析了 Flask 生态中 REST 扩展的现状,提出了“自组工具链”的思路。
- 通过示例了解了使用原生 Flask 实现 API 核心功能的实践路径。
希望本教程能为你使用 Flask 进行 API 开发提供一个良好的起点。


007:用MicroPython触摸物理世界 🚀

在本节课中,我们将学习如何使用MicroPython与物理世界进行交互。我们将从认识可编程硬件开始,了解MicroPython的优势,并探索如何利用它控制各种传感器和执行器,最终实现一个简单的心率监测项目。
认识可编程硬件
我们日常生活中使用的许多家电,例如空调、洗衣机,内部都包含像8051这样的单片机。这类芯片价格便宜,但对于初学者而言,其开发门槛较高,主要体现在调试手段和库支持方面。
另一种选择是STM32系列芯片的开发板。虽然性能强劲,支持也较为丰富,但作为入门玩具,其调试过程依然可能较为复杂。
在开源硬件领域,Arduino和树莓派对新手非常友好,可以快速搭建硬件平台并控制各种设备。然而,Arduino开发板相对较贵,而树莓派4B(4GB版本)全套价格可能在500元以上,对于个人爱好者而言成本较高。
我的选择:ESP32
我的最终选择是ESP32模组。其前代产品ESP8266价格低廉(不到10元),也能运行MicroPython,但性能不如ESP32。
ESP32原生支持Wi-Fi和蓝牙。这意味着在开发硬件时,可以直接通过HTTP API与设备交互,或者在没有互联网时通过蓝牙与手机交互。不到10杯奶茶的价格,你就可以拥有一个自己的电子玩具。
更重要的是,它支持丰富的生态,尤其是MicroPython固件。我选择MicroPython是为了统一开发语言,方便在不同项目中应用。
什么是MicroPython?
MicroPython是基于Python 3语法优化的一个子集,能够运行在32位微处理器上。它可以运行一些简单的嵌入式操作系统。
支持的开发板
以下是几种支持MicroPython的常见开发板:
- Pyboard:基于STM32F4芯片,社区支持良好。使用简单,只需将Python脚本按规则放入SD卡即可开机执行。
- WiPy:使用乐鑫ESP32芯片,具备Wi-Fi和蓝牙能力。其开发工具对初学者可能稍显复杂。
- ESP32:与WiPy使用相同的芯片模组。支持I2C、串口、GPIO等标准接口,同样具备Wi-Fi和蓝牙功能,便于与外界交互。
外围硬件与交互
仅有开发板无法直接感知物理世界。我们需要外围硬件的支持。以下是一些常用的模块:
- 传感器:如DHT系列温湿度传感器(通过I2C接口通信)、陀螺仪、心率传感器(如MAX30102)。
- 显示设备:如OLED屏幕,用于实时显示数据。
- 执行器:如继电器,可用于控制家中的各种开关设备。
这些模块通常通过I2C总线或GPIO口与ESP32等开发板通信。只要硬件支持相应协议,MicroPython就可以控制它们。
实践案例:心率监测
上一节我们介绍了基础硬件,本节我们来看看一个具体的实践项目:制作一个心率监测器。
该项目需要以下设备:
- ESP32开发板
- MAX30102心率血氧传感器模块
- OLED显示屏
工作原理:MAX30102通过光电传感器读取血液的血氧浓度和心率数据,并通过I2C总线将数据发送给ESP32。ESP32处理数据后,再通过I2C总线将实时心率显示在OLED屏幕上。
核心通信代码结构示意:
import machine
import max30102
import ssd1306
# 初始化I2C总线
i2c = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(21))
# 初始化传感器和显示屏
sensor = max30102.MAX30102(i2c)
display = ssd1306.SSD1306_I2C(128, 64, i2c)
while True:
heart_rate = sensor.get_heart_rate() # 获取心率数据
display.fill(0)
display.text('HR: ' + str(heart_rate), 0, 0)
display.show()
另一个想法:智能门禁
这个想法来源于一位同事的需求。他住在老小区,希望做一个能联网控制楼下门禁的装置。
所需硬件非常简单:
- ESP32开发板
- 继电器模块
实现思路:将继电器连接到门禁系统的开门按钮线上。通过MicroPython编写一个简单的Web服务器运行在ESP32上。当用户通过手机访问该Web服务器时,ESP32控制继电器短接模拟按钮按下,从而实现远程开门。
公式化描述:手机APP/网页 -> HTTP请求 -> ESP32 Web服务器 -> GPIO输出高电平 -> 继电器闭合 -> 门禁电路导通
总结与展望
本节课中,我们一起学习了如何利用MicroPython和ESP32等硬件触摸物理世界。我们从硬件选型讲起,了解了MicroPython的特性,认识了常用的传感器和执行器,并探讨了心率监测和智能门禁两个实践案例。
MicroPython降低了嵌入式开发的门槛,让软件开发者也能轻松入门硬件编程。其低成本和高灵活性,使得它在个人项目、原型开发乃至青少年编程教育领域都有很大的应用潜力。希望本次分享能为你打开一扇新的大门,享受创造与交互的乐趣。
提示:所有项目均需注意用电安全,特别是在连接220V市电时。建议从低电压(如5V、12V)的直流设备开始实践。

(注:文中提到的具体价格可能随时间变化,请以实际市场为准。)
008:Python在物联网开发中的应用


概述
在本节课中,我们将学习Python如何应用于物联网项目开发。我们将从物联网项目的整体架构入手,分析典型项目的技术栈,并探讨Python如何简化并加速物联网终端、网关及云端的开发流程。
物联网项目架构概述
一个完整的物联网项目通常包含三个核心层面:云端、管道和终端设备。
云端是服务端程序,负责数据处理、存储和业务逻辑。管道是通信网络,负责数据传输。终端设备是与物理世界交互的硬件,负责数据采集和指令执行。
物联网的交互不仅限于人与物,还包括物与物之间的自动交互。终端设备依赖于各种硬件,如处理器、传感器、执行器(如智能锁)和人机交互接口(如屏幕)。
典型物联网项目剖析
以下是两种典型的物联网项目网络拓扑结构及其技术栈。
共享单车系统
共享单车系统采用单层网络拓扑。终端设备(单车)通过2G/4G或NB-IoT网络直接连接云端,中间没有其他网络节点。
从软件开发角度看,其技术栈主要包括:
- 终端设备开发:涉及单片机程序,用于控制车锁、采集GPS数据等。
- 服务端开发:处理用户请求、计费、车辆调度等业务逻辑。
- (手机App作为用户交互前端,通常不在此技术栈核心讨论范围内。)
智能家居系统
智能家居系统采用双层网络拓扑。它包含一个局域网(如Zigbee、蓝牙)和一个外网。
- 局域网:由网关和多个终端设备(如传感器、智能灯泡)组成。终端设备只与网关通信。
- 外网:网关通过Wi-Fi、2G/4G等网络连接至云端。
因此,其技术栈更为复杂,包含三个层面:
- 终端开发:通常是单片机程序,用于驱动传感器、控制继电器等硬件。
- 网关开发:通常是嵌入式Linux应用程序,负责组建局域网、处理本地策略、与云端通信。
- 云端开发:服务端程序,提供数据存储、分析和用户界面。
上一节我们介绍了物联网项目的两种典型结构,接下来我们深入看看每个层面的具体技术挑战。
Python如何破解物联网开发难题
Python可以从以下三个层面助力物联网开发,提升效率。
1. Python与终端开发:MicroPython
MicroPython是运行在微控制器上的Python 3实现,极大简化了终端设备开发。
- 开发便捷性:无需专用烧写器和复杂的IDE。通过USB连接,可直接在文件系统中编程或使用REPL交互环境。
- 硬件接口支持:对GPIO、I2C、SPI、UART、PWM等常用硬件接口提供了良好支持。
- 生态丰富:支持数十种主流单片机,降低了硬件选型依赖。
- 降低对芯片厂商的依赖:代码在不同硬件平台间移植性更好,减少因更换芯片供应商导致的重复工作。
2. Python与网关开发
物联网网关的软件开发本质上是应用程序开发。Python作为高级语言,在此领域优势明显。
- 环境支持:许多嵌入式Linux系统的BSP已包含Python。若无,自行移植的成本也相对较低。
- 硬件驱动:Python生态(如
RPi.GPIO、spidev)已包含常见硬件接口驱动。如需特殊驱动,可用Python或C编写模块供调用。 - 本地数据处理:Python对SQLite等轻型数据库支持完善,适合网关本地数据存储。
- 网络通信:丰富的库支持HTTP(如
requests)、MQTT(如paho-mqtt)等物联网常用协议。 - 性能优化:对性能敏感的核心模块可用C/C++编写,再封装为Python库调用,兼顾开发效率与运行效率。
3. Python与云端开发
Python在物联网云端开发中扮演着核心角色。
- 快速对接云平台:AWS IoT、阿里云IoT、OneNET等主流物联网云平台均提供Python SDK,可快速集成。
- 快速自建服务:使用Django、Flask等框架能迅速搭建Web服务端。
- 强大的数据可视化:Matplotlib、Plotly等库能轻松将物联网数据转化为直观图表。
- 与AI/大数据融合:物联网产生海量数据。Python是机器学习(TensorFlow, PyTorch)和大数据处理(Pandas)的主流语言,便于开发智能物联网应用。
综上所述,使用Python几乎可以打通物联网“云-管-端”全栈开发。
实战案例:Python驱动的智慧农业系统
本节我们将通过一个智慧农业系统的实战案例,具体看Python如何应用于一个相对复杂的物联网项目。
该项目网络拓扑包含终端、网关和云端三层,实现了环境监测、自动灌溉、安防预警等功能。
系统架构与功能
- 终端设备:基于ESP32单片机(运行MicroPython),连接土壤湿度、光照等多种传感器,以及水泵、灯光等控制器,并通过LoRa模块与网关通信。
- 网关:基于树莓派3B+(运行Python),负责接收终端数据,通过2G模块与云端通信,并可本地存储数据、触发电话报警。
- 云端:基于Django框架开发,提供设备管理、数据可视化、智能策略(如自动灌溉规则)制定等功能。
各层Python实现要点
以下是该系统中Python技术栈的关键应用:
-
终端 (MicroPython):
# 示例:读取土壤湿度传感器(通过ADC) import machine adc = machine.ADC(machine.Pin(34)) # 假设传感器接在GPIO34 moisture_value = adc.read() # 将数据通过LoRa发送 # ... lora.send(data) ... -
网关 (Python on Raspberry Pi):
# 示例:通过串口读取LoRa数据,并通过MQTT上报云端 import serial import paho.mqtt.client as mqtt ser = serial.Serial('/dev/ttyS0', 9600) # 连接LoRa模块 mqtt_client = mqtt.Client() mqtt_client.connect("cloud.server.com", 1883) while True: data = ser.readline() mqtt_client.publish("sensor/data", data) -
云端 (Django):
# 示例:Django视图处理设备心跳,更新在线状态 from django.http import JsonResponse from .models import Device def device_heartbeat(request, device_id): try: device = Device.objects.get(id=device_id) device.is_online = True device.last_seen = timezone.now() device.save() return JsonResponse({"status": "ok"}) except Device.DoesNotExist: return JsonResponse({"error": "Device not found"}, status=404)
设备健康管理
针对硬件设备可能离线的问题,系统通过心跳机制实现设备健康监控:
- 终端定期向云端发送心跳包,包含设备ID、电量等信息。
- 云端记录设备最后在线时间。
- 管理界面展示设备在线/离线状态,长时间离线的设备会触发告警,以便运维人员及时处理。

总结
本节课我们一起学习了Python在物联网开发中的强大助力。我们从物联网的“云-管-端”架构出发,分析了共享单车和智能家居两种典型项目的技术栈。随后,我们深入探讨了Python通过MicroPython简化终端开发、作为高级语言高效开发网关应用、以及快速构建云端服务和对接AI能力的具体方式。最后,通过一个智慧农业系统的实战案例,我们看到了Python如何贯穿一个完整物联网项目的开发,实现高效、智能的解决方案。希望本课程能为你打开使用Python进行物联网开发的大门。
009:当知识图谱遇见Python

在本节课中,我们将学习知识图谱的基本概念、构建流程,并了解如何利用Python生态中的工具来构建一个新闻概念知识图谱。我们将从知识图谱出现的背景讲起,逐步深入到具体的构建技术和实践案例。
1. 前沿:知识图谱的历史必然性
上一节我们介绍了课程概述,本节中我们来看看知识图谱为何在当今时代应运而生。
我们现在身处一个AI时代。有一种观点认为,一个智能的AI系统等于知识加推理。
举个例子,如果我们问机器“1MB等于多少KB”,机器能直接给出答案“1024”。机器的潜台词是它已经储备了这个知识。如果我们问机器“Donald Trump的出生地在哪里”,机器可能需要先解析问题,将其转化为结构化的查询(如(Donald Trump, 出生地, ?)),然后基于后台的知识库进行检索和推理,最后得到答案“纽约市”。机器的潜台词是它会推理。
从知识工程的角度,我们希望一个智能的系统(Smart Machine)能够感知数据的分布,并能从认知层面理解数据的意义。这与符号主义的认知是一致的:认知即计算,知识是形式化的,是智能的基础。智能的核心在于知识的表示、推理和运用能力。
然而,传统的知识工程领域严重依赖专家和用户干预来定义规则和逻辑(专家系统),这存在几个问题:
- 知识获取困难:许多领域知识和常识是隐性的、过程性的,难以定义和表示。
- 知识应用困难:在开放场景中,知识的边界难以确定,且需要常识支撑。
- 难以处理异常:定义的规则可能无法覆盖所有特例(例如,“鸟会飞”的规则不适用于鸵鸟和企鹅)。
大数据、强大的机器学习模型和计算能力的兴起,为新时代的知识工程奠定了基础:
- 大数据为大规模知识获取提供了支撑。
- 机器学习算法使我们能够从数据中自动化地学习高质量知识。
- 强大算力使得处理海量数据成为可能。
知识获取的方式从传统的“自上而下”(先有领域模型,再填充实例)转变为“自下而上”(从大数据中学习知识)。知识图谱正是在此背景下应运而生,它突破了传统知识库在规模和质量上的瓶颈。
2. 知识图谱构建技术栈
上一节我们探讨了知识图谱出现的必然性,本节中我们来看看如何通过自动化、数据驱动的方式来构建知识图谱。
知识图谱本质上是一种大规模语义网络。其节点可以是实体、概念、属性甚至事件,边则表示它们之间的关系。
知识图谱的应用场景广泛:
- 智能搜索:从返回网页链接到直接给出精准答案(如“刘德华的妻子是谁”)。
- 精准推荐:在电商等场景中,结合用户行为和领域知识进行个性化推荐。
- 智能问答:支持多轮、多模态的对话系统。
知识图谱的构建主要有两种方式:
- 手动构建:如Cyc、WordNet。质量高但规模小,依赖专家。
- 自动化构建:如Google Knowledge Vault、Microsoft Concept Graph。规模大,是当前趋势,但可能包含更多噪声。
一个完整的自动化知识图谱构建流程通常包括以下阶段:
知识抽取
目标是从各种数据源中提取出结构化知识(通常以(实体,关系,实体)三元组形式)。数据源分为:
- 结构化数据(如数据库表格)
- 半结构化数据(如HTML表格)
- 非结构化数据(如纯文本)
工业界常用前两者,学术界则更关注从非结构化文本中抽取知识,这更依赖NLP技术。
知识抽取主要分为两个学派:
- 限定域关系抽取:关系的类型(Schema)是预先定义好的有限集合。任务通常被建模为一个分类问题,即判断句子中两个实体属于预定义关系中的哪一种。
- 开放域关系抽取:关系的类型不是固定的,而是从句子上下文中动态抽取出的词语或短语来描述。例如,从句子“姚明出生于上海”中抽取出关系“出生于”。
以下是主流方法简介:
- 基于模板的方法:使用预定义的模式(如
X was acquired by Y)在文本中匹配。 - 基于机器学习的方法:从早期的特征工程、核方法,发展到如今的深度学习方法。
- 基于弱监督的方法(如远程监督):假设如果两个实体在知识库中存在某种关系,那么所有包含这两个实体的句子都可能表达了这种关系。
- 开放信息抽取:如华盛顿大学开发的OpenIE系统,专门从文本中抽取开放域的三元组。
知识融合
将从不同来源抽取的知识进行整合,消除冲突和冗余。分为:
- 垂直融合:高层本体(模式层)与底层实例数据的融合。
- 水平融合:不同知识库之间对齐等价格实体(如百度百科的“刘德华”和维基百科的“刘德华”指向同一实体),合并关系等。
关键技术包括实体对齐、关系合并、冲突检测等。
知识加工
对融合后的知识进行进一步处理,包括:
- 知识推理:发现隐含的知识。
- 符号推理:基于逻辑规则演算。
- 数值推理:基于表示学习(如TransE、张量分解)。
- 融合推理:结合符号与数值方法。
- 质量评估:对知识的可信度进行量化,过滤低质量知识,保证知识库整体质量。
知识更新
知识图谱需要与时俱进,反映世界的变化。更新方式包括:
- 数据层更新 vs 模式层更新
- 全局更新 vs 增量更新
3. Python生态中的图谱工具
上一节我们梳理了知识图谱构建的整体框架,本节中我们来看看Python生态中有哪些工具可以助力这一过程。
自然语言处理:Stanford CoreNLP
这是一个功能强大的NLP工具包,提供Python接口。它能完成:
- 命名实体识别:识别文本中的人名、地名、机构名等。
- 共指消解:确定代词(如“他”、“它”)或别名所指代的真实实体。
- 依存句法分析:分析句子中词语间的语法依赖关系。
这些功能是进行知识抽取(尤其是从文本中)的基础。
开放信息抽取:OpenIE
华盛顿大学开发的开放信息抽取系统,可以从句子中直接抽取(主语,关系,宾语)三元组。虽然核心是Java编写,但可以通过Python组件(如openie)调用其接口。
示例:对于句子“The US President gave a speech in the White House”,OpenIE可能抽取出(The US President, gave, a speech)和(a speech, in, the White House),并为每个三元组提供置信度分数。
图数据管理与分析
- NetworkX:一个用于创建、操作和研究复杂网络结构的Python库。可以用于计算最短路径、分析网络特性等。
- Gephi:一款开源的可视化与探索软件,虽然非纯Python工具,但常与Python结合使用。它支持复杂网络分析,并能将CSV或JSON文件导入生成可视化图谱。
4. 实战案例:新闻概念知识图谱构建
上一节我们介绍了Python中的相关工具,本节中我们将通过一个具体案例,把这些工具和流程串联起来,展示如何构建一个新闻概念知识图谱。
动机:当人们阅读大量同一主题的文档(如10篇关于美国大选的新闻)时,很难手动记住并组织所有关键实体和关系。我们希望通过自动化方式,从文档集合中抽取出核心概念和关系,并以图谱形式直观展示,帮助用户快速把握主题核心。
任务分解:
- 候选事实抽取:从非结构化文本中抽取出大量三元组。
- 知识过滤:筛选出与主题真正相关的、高质量的三元组。
- 概念知识图谱构建:将过滤后的三元组进行融合、链接,形成图谱。
步骤一:知识抽取
- 文档排序:从文档集合中筛选出与主题最相关的文档。
- 共指消解:将文档中的代词(如“他”、“该国”)替换为具体的实体名称,提升后续抽取的准确性。
- 句子排序:在文档内挑选出信息量丰富的句子。
- 开放信息抽取:使用OpenIE系统对处理后的句子进行三元组抽取,并得到每个三元组的置信度。
步骤二:知识过滤
目标是从海量抽取的三元组中,保留与领域主题紧密相关的部分。我们将此问题形式化为一个整数规划问题,通过优化算法进行求解,以最大化保留三元组与主题的相关性。
步骤三:图谱构建
- 合并等价概念:基于字符串特征或搜索引擎(如维基百科链接),将指代同一实体的不同表述(如“特朗普”、“Donald Trump”、“川普”)进行合并。
- 链接命名实体:利用已有知识库(如维基百科)的实体链接信息,将实体规范化。
- 人工校验与标签:引入专家知识对部分结果进行校验,并为实体和关系添加类型标签。
- 图谱生成与可视化:将处理后的三元组构建成图结构,并利用可视化工具进行展示。
结果示例:输入多篇美国大选新闻,最终生成的知识图谱可能显示:“特朗普”与“希拉里”之间存在“竞争”关系;“希拉里”与“邮件门”事件相关联;“邮件门”事件“影响”“选民支持率”等。通过图谱,用户可以一目了然地把握事件全貌。
实验验证:通过句子级别的抽取评估和用户调研,验证了该方法的有效性(如F1值达到0.58),并对生成图谱的概念覆盖率、置信度等质量指标进行了分析。
总结

本节课中我们一起学习了知识图谱的核心概念与构建方法。我们从知识图谱出现的AI时代背景讲起,理解了其“知识+推理”的核心思想。随后,我们系统性地介绍了自动化构建知识图谱的全流程技术栈,包括知识抽取、知识融合、知识加工和知识更新。接着,我们探索了Python生态中支持图谱构建的关键工具,如Stanford CoreNLP、OpenIE、NetworkX等。最后,通过一个新闻概念知识图谱构建的实战案例,我们将理论、工具和流程串联起来,展示了从非结构化文本到直观概念图谱的完整实现路径。希望本教程能帮助你入门知识图谱这一充满魅力的领域。
010:合同风险预测模型构建教程 🚀


概述
在本教程中,我们将学习如何利用Python进行自然语言处理,并构建一个用于预测合同风险的模型。我们将从NLP的基础流程开始,探讨多语言处理的差异,最终深入到合同风险预测模型的实战构建思路。
第一节:NLP基础流程与探索性数据分析 📊
上一节我们概述了课程内容,本节中我们来看看自然语言处理的基础流程以及一种重要的数据分析方法。
自然语言处理通常从语料开始。语料来源可以是网络爬虫、公司或大学已有的语言数据,或是公开的语料库。获取语料后,需要进行前处理,例如删除特殊符号等无用信息。处理完成后,即可进行分词,并在此基础上进行语义解释分析。
自然语言处理的核心是将文字转化为机器可读的数字。因此,分词后通常需要进行向量化,将词语转换为向量或张量,以便输入模型进行学习。有时,语义分析的结果也会作为特征输入机器学习模型,使模型能学习到语义知识。
分词后还可以进行一项重要工作:探索性数据分析。EDA是一种分析数据的方法,旨在探索数据的内在结构和特征。在NLP中,不同领域的语言有其独特的特征和倾向。如果不了解手中数据的特性就盲目处理,很容易中途迷失方向。
以下是使用语料库进行词类分析的一种方法:
- 收集目标领域的语料(例如10万条合同文章)。
- 从基准语料库(如日语BCCWJ语料库)中提取其他领域(如图书、新闻、杂志)的语料作为对比基准。
- 使用相同的分词方法处理所有语料。
- 统计各领域语料中不同词类(如名词、动词、形容词)的出现频次,并制作分布表。
- 将高频部分用颜色高亮显示,形成可视化图表。
通过对比合同文章与法律文章、图书、杂志等领域的颜色分布图,可以发现合同文章与法律文章在词类分布上较为相似,而与其他领域差异较大。这种可视化分析能帮助我们把握数据的领域特征,从而在选择模型(例如,对于语义转折较多的合同文本,传统的词袋模型可能效果不佳)和解释结果时更有把握。
总结一下,使用语料库进行EDA的目的是掌握数据的特征,这能提高后续工作的处理效率,帮助我们有把握地选择模型,并能更好地理解模型的输出结果。
第二节:多语言NLP处理攻略 🌍
上一节我们介绍了NLP基础流程和EDA,本节中我们来看看处理不同语言时的差异和工具。
英语、汉语和日语在分词处理上有所不同。英语单词通常由空格分隔,分词相对简单。汉语需要进行分词,将连续的汉字序列切分成有意义的词语。日语则更为复杂,除了分词,还需要进行“同一词形归一化”处理。
“同一词形归一化”是指将词语的不同变化形式统一为标准形式。例如,日语的动词有活用变形,名词可能有汉字、平假名、缩写等多种写法,都需要统一。虽然很多工具可以自动处理,但某些情况仍需人工干预。
以下是各语言常用的分词工具:
- 英语:推荐NLTK,它简单易用,适合入门。
- 汉语:推荐
jieba分词。 - 日语:有以下几种选择:
- MeCab:最著名的分词工具,基于马尔可夫模型,需搭配词典使用。
neologd词典适合处理新词,unidic词典更适合数据分析。 - Juman:分词精度高,但处理速度较慢。
- GiNZA:2019年发布的新工具,集成分词和语义分析功能,较为方便。
- MeCab:最著名的分词工具,基于马尔可夫模型,需搭配词典使用。
此外,日中语义分析也有不同。汉语分词后,需要额外标注主谓宾等语法角色。日语则借助格助词来标识,例如“が”常提示主语,“を”常提示宾语。在合同分析中,准确区分甲方(动作发出者)和乙方(动作接受者)至关重要,因此必须处理好语义角色。
第三节:合同风险预测模型实战分享 ⚖️
上一节我们探讨了多语言处理的差异,本节我们将进入实战,学习构建合同风险预测模型的思路。
合同审查通常关注两点:一是合同是否遗漏了必备条款;二是合同内容是否对己方不利。因此,我们的模型目标也有两个:目的1是检查合同条款的相似性(查找缺失条款);目的2是识别合同中的不利条款。
使用Python实现合同风险预测的思路如下:
- 实现目的1(条款相似性):计算所有条款之间的相似度,设定阈值
threshold来判断内容是否一致。这可以使用无监督学习方法。 - 实现目的2(不利条款识别):判断条款对甲方有利、乙方有利还是平等。这需要使用监督学习方法,构建分类模型。
我们尝试了四种方法来预测合同风险:
- Word2Vec:词嵌入方法。
- Doc2Vec:文档嵌入方法,较新。
- 神经网络:深度学习模型。
- TF-IDF:传统的词频-逆文档频率方法。
在我们的数据上,Word2Vec效果优于Doc2Vec。具体操作是,将句子中的每个词向量取出后求平均,以此代表句子向量,再进行相似度计算。
实战示例:查找与合同中“第五条”最相似的条款。
使用四种方法比较后,Word2Vec能准确找到同为“违约责任”的条款,且相似度数值区分度好(该高的高,该低的低)。Doc2Vec容易将不相关条款相似度归零,TF-IDF则容易产生大量0或1的极端值,都需要较多调优。
但需注意,Word2Vec可能计算出“甲方”和“乙方”向量相似度高,这在通用领域可行,但在合同中,双方利益对立,这种相似性不可接受。
解决方案:
- 使用考虑上下文关系的模型:如
BERT等预训练模型。但合同数据敏感,获取大量标注数据困难。 - 采用监督学习分类模型:利用公司律师进行数据标注。我们采用了两层分类器:
- 第一层:预测条款的种类(多分类问题)。因为不同种类条款的“有利性”判断标准不同。
- 第二层:在确定条款种类后,再预测该条款对甲方有利、乙方有利还是平等(多分类问题)。
模型效果:目前日语版本的模型,预测一致条款的正确度达85%,条款种类分类正确度达91%,有利方判断正确度达90%。我们正在尝试使用深度学习模型进一步提升精度,但同时也致力于开发可解释性强的机器学习模型,这是当前的重要目标。
总结与建议 💡
本节课中,我们一起学习了NLP的基础流程、多语言处理差异以及构建合同风险预测模型的完整思路。
最后想强调的是:在开始任何NLP项目前,首先要明确你想要解决的具体问题。将业务问题转化为明确的统计或机器学习问题。如果不知从何下手,可以先进行探索性数据分析来把握数据特征。了解数据特征后,就能更轻松地选择合适的模型。解决方案有很多,从简单的浅层模型到复杂的深度学习模型,可以根据可用的时间、计算资源来权衡选择。目标明确、模型选好后,剩下的就是动手实践。

核心要点回顾:
- EDA是关键:
可视化分析能揭示数据领域特征,指导模型选择。 - 工具选择:英语用
NLTK,中文用jieba,日语可选MeCab、Juman或GiNZA。 - 合同模型思路:结合
无监督学习(相似度计算)和监督学习(两层分类)来分别实现条款查漏和风险识别。 - 实践路径:
明确问题->EDA分析数据->选择合适的模型->实现与优化。
011:一次关于 SQLAlchemy Session 的项目排错经验分享 🐛

在本节课中,我们将学习一个在 Flask 项目中使用 SQLAlchemy 时遇到的真实并发问题。我们将通过一个案例,理解 Session 管理不当如何导致数据更新丢失,并学习如何避免此类问题。
项目背景与问题描述
我们的项目使用 Flask 框架,并集成了 SQLAlchemy 作为 ORM 工具,数据库选用 MySQL。由于预计用户量较大,我们采用了分库分表的设计。
具体问题是:在后台大批量修改用户数据时,发现部分用户的修改会失败。经过排查,我们定位到两个用于修改用户数据的接口(仅修改字段不同)。当并发请求这两个接口时,总有一个接口会修改失败;但串行请求时则没有问题。
问题复现与分析
当同事描述这个 Bug 时,我首先想到,串行修改成功说明接口本身逻辑基本正确,问题可能出在数据库或两个接口的关联环节。
以下是 Bug 的详细描述:
- 数据库中存在一个用户记录,包含
name和age字段。 - 接口一:将
name从“张三”改为“李四”。 - 接口二:将
age从 18 改为 20。 - 并发调用这两个接口后,发现数据库中只有一个字段被更新,另一个接口的修改似乎失败了。
我最初根据描述模拟了代码,但并未复现问题。这让我感到困惑,于是重新梳理了项目情况:Flask + SQLAlchemy + 分库分表。考虑到分库分表可能导致每次操作都新建 Session,我怀疑问题出在 Session 的管理上。
查看代码后,果然发现了问题所在。由于分库分表,每次进行数据库操作时都需要获取一个新的 Session。在我们的业务代码中,获取用户对象时创建了一个 Session,保存用户对象时又创建了另一个 Session。在并发场景下,这两个 Session 的创建和保存操作存在时间差,导致了数据冲突。
问题根源与模型推演
为了更清晰地理解,我建立了以下问题模型:
- 数据库初始状态:
name='张三',age=18。 - 进程一(修改名字)和进程二(修改年龄)各自获取了一个独立的 Session,并且都从数据库读到了同一份原始数据。
- 进程二先将
age改为 20 并提交(Session 二保存),此时数据库变为name='张三',age=20。 - 紧接着,进程一将其 Session 一中持有的旧数据(
age仍为 18)的name改为“李四”并提交。这会导致数据库状态被覆盖为name='李四',age=18。
结果:进程二对 age 的修改被进程一无意中“回滚”了,造成了数据更新丢失。
解决方案与验证
问题的核心在于 Session 的不当复用和隔离。在业务代码中,两次操作使用了不同的 Session,导致它们持有数据的不同副本,无法感知彼此的更改。
以下是模拟问题复现的关键代码片段(方框内为第二次获取 Session 的调用):
# 伪代码示意:错误的方式(每个操作独立获取Session)
def update_name(user_id):
session1 = get_new_session() # 第一次获取Session
user = session1.query(User).get(user_id)
user.name = "李四"
session2 = get_new_session() # 第二次获取Session(错误!)
session2.merge(user) # 或 session2.add(user)
session2.commit()
def update_age(user_id):
session1 = get_new_session()
user = session1.query(User).get(user_id)
user.age = 20
session2 = get_new_session() # 同样的问题
session2.merge(user)
session2.commit()
当并发执行上述函数时,就会触发所述的数据覆盖问题。
修复方案是确保在同一业务操作单元内使用同一个 Session。我们修改了代码,使获取对象和保存对象在同一个 Session 上下文中完成。修改后,并发测试显示两个字段都能被正确更新,问题得以解决。
经验总结与启示
本节课中,我们一起学习了一个由 SQLAlchemy Session 隔离引发的并发数据更新丢失案例。通过这次排错,我们得到了以下几点重要启示:
- 敬畏数据:生产环境的数据至关重要,修复此类问题耗时耗力,应尽力在测试阶段杜绝。
- 敬畏测试:我们虽然有单元测试,但并未覆盖接口并发场景。这次问题是在后台大规模操作时才暴露的,提醒我们需要加强并发和集成测试。
- 敬畏代码:尤其是框架和基础组件的使用方式。对于 SQLAlchemy,必须清晰地理解 Session 的生命周期、隔离级别以及“同一性(Identity Map)”模式。在涉及数据修改的场景中,确保操作在正确的 Session 上下文中进行至关重要。

希望这个实际案例能帮助你理解 SQLAlchemy Session 的核心概念,并在未来开发中避免类似的陷阱。

浙公网安备 33010602011771号