PyCon-2019-会议笔记-全-
PyCon 2019 会议笔记(全)
001:使用三个Python深度学习框架创建肥皂剧脚本 🎭


在本教程中,我们将学习如何结合深度学习和肥皂剧的趣味性,使用Python生成肥皂剧脚本。我们将探讨深度学习的基本概念,并比较Keras、TensorFlow和PyTorch这三个流行框架在文本生成任务中的应用。

概述:艺术与技术的交汇点 🤖
近年来,人工智能在创意领域的应用引发了广泛关注。例如,有创作者使用递归神经网络(RNN)生成了由大卫·哈塞尔霍夫“主演”的AI短片剧本。另一个例子是,有人尝试用《电锯惊魂》系列电影的剧本训练神经网络来生成恐怖片脚本,结果却出现了“特朗普”和“鲸鱼”等原剧本中不存在的词汇。这些案例揭示了神经网络的工作原理:它通过学习我们提供的文本语料库来生成内容。如果语料库中没有某些词汇,模型理论上不应生成它们。这引发了关于创造力与技术交集的思考,也是本教程探索的起点:我们能否用深度学习来生成肥皂剧脚本?
为什么选择肥皂剧? 📺


肥皂剧(Telenovela)是一种源自拉丁美洲的电视剧形式,特点是情节夸张、情感冲突强烈且通常有明确的结局。全球约有20亿观众观看拉丁美洲的肥皂剧,约占世界人口的三分之一,其影响力巨大。这类剧集通常有固定的情节模式:复杂的家庭关系、失而复得的爱情、戏剧性的冲突,最终以幸福结局收场。例如,在著名的墨西哥肥皂剧《玛丽亚·拉·德尔·巴里奥》中,就有“Esquincla babosa!”(你这讨厌的丫头!)这样的经典台词和史诗般的打斗场面。这种结构化和情感丰富的文本,使其成为训练文本生成模型的理想素材。
从机器学习到深度学习 🧠
在深入具体技术之前,我们需要理解一些核心概念。机器学习是计算机科学的一个领域,它使计算机能够在没有明确编程的情况下进行学习。其创始人阿瑟·塞缪尔将其定义为:“一个研究领域,它使计算机能够在没有明确编程的情况下学习。”


汤姆·米切尔给出了一个更具体的框架:一个计算机程序从关于某类任务 T 的经验 E 中学习,并通过性能指标 P 来衡量其改进。如果它在任务 T 上的性能(由 P 衡量)随着经验 E 的增加而提高,那么它就是在学习。


公式表示:
学习 = 提高( P | T, E )

深度学习是机器学习的一个子领域,它使用被称为神经网络的结构来驱动学习过程。我们可以将发展历程概括为:
- 人工智能:程序能适应环境(如自动门传感器)。
- 机器学习:程序能从数据中学习模式(如垃圾邮件过滤器)。
- 深度学习:程序使用复杂的神经网络进行学习,并能进行推断(如图像识别系统不仅能识别“苹果”,还能联想相关特征)。
核心任务:文本生成与循环神经网络 📝
我们的目标是文本生成,这属于序列处理问题。在深度学习中,循环神经网络(RNN) 特别适合处理这类序列数据(如文本、时间序列)。RNN能够处理前后依赖关系,这对于理解语言上下文至关重要。

神经网络的基本单元是神经元。一个神经元通常包含三个部分:
- 输入:接收数据(如一个单词或字符的数值表示)。
- 权重:每个输入关联一个权重,代表其重要性。
- 激活函数:一个非线性函数(如Sigmoid、ReLU),用于决定神经元是否被“激活”并将信号传递到下一层。

神经元的工作流程可以简化为:
输出 = 激活函数( Σ (输入 * 权重) + 偏置 )

在训练过程中,网络通过反复迭代来调整权重,以最小化预测误差。

Python中的三大深度学习框架 ⚙️
在Python中,有三个广泛使用的深度学习框架可以帮助我们构建RNN模型:Keras、TensorFlow和PyTorch。选择框架时,可以考虑以下三个问题:
- 需要多少技术专长才能上手?
- 项目的具体要求是什么?(如数据量、性能需求)
- 框架的易用性如何?
以下是它们在构建RNN时的简要对比:

1. Keras:高级API,快速上手


Keras提供了一个用户友好的高级API,抽象了许多底层细节,非常适合初学者快速构建原型。

代码示例:构建一个简单的RNN模型
from keras.models import Sequential
from keras.layers import LSTM, Dense



model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars)))) # 添加LSTM层
model.add(Dense(len(chars), activation='softmax')) # 添加输出层
model.compile(loss='categorical_crossentropy', optimizer='adam') # 编译模型
model.fit(x, y, batch_size=128, epochs=20) # 训练模型
特点:像搭积木一样构建模型,代码简洁明了。


2. TensorFlow:灵活且功能强大



TensorFlow提供了更底层的操作,灵活性更高,但需要编写更多的代码来定义模型结构。


代码示例:定义TensorFlow计算图
import tensorflow as tf
# 定义输入占位符
inputs = tf.placeholder(tf.float32, [None, maxlen, len(chars)])
# 定义RNN单元
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(128)
# 初始化状态
initial_state = lstm_cell.zero_state(batch_size, tf.float32)
# 展开RNN
outputs, state = tf.nn.dynamic_rnn(lstm_cell, inputs, initial_state=initial_state)
# ... 定义损失函数和优化器
特点:需要显式定义计算图和张量操作,控制力强。
3. PyTorch:动态图与直观调试


PyTorch使用动态计算图,使得模型构建过程更像标准的Python编程,调试非常方便。



代码示例:定义PyTorch模型类
import torch
import torch.nn as nn


class RNNModel(nn.Module):
def __init__(self):
super(RNNModel, self).__init__()
self.lstm = nn.LSTM(input_size, hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 定义前向传播逻辑
lstm_out, _ = self.lstm(x)
output = self.fc(lstm_out[:, -1, :])
return output
特点:使用Python类定义模型,前向传播逻辑清晰,易于理解和调试。
实践:生成肥皂剧脚本 🎬

在实际操作中,无论选择哪个框架,文本生成通常遵循以下步骤:
- 数据准备:收集肥皂剧剧本文本,进行清洗。
- 文本向量化:将字符或单词转换为神经网络可以理解的数值形式(如独热编码)。
- 构建模型:使用上述任一框架构建RNN(通常使用LSTM单元)。
- 训练模型:用准备好的数据训练模型,调整权重。
- 生成文本:使用训练好的模型,给定一个起始“种子”文本,让其预测后续字符/单词,循环生成新文本。


在实践中,初学者可能会发现,使用Keras能最快速地获得一个可运行的模型,尤其当它与TensorFlow后端结合时,既能享受高级API的简便,又能利用TensorFlow的生态系统。对于小规模数据集和实验性项目,这是一个高效的选择。


生成的文本长度和质量与训练数据量、模型复杂度以及训练轮数密切相关。初期可能只能生成较短的、有时略显荒谬的片段(就像“鲸鱼”的例子),但随着调整和优化,可以逐渐生成更连贯、更具戏剧性的“肥皂剧”对白。

总结 🏁
本节课我们一起探索了使用深度学习生成肥皂剧脚本的奇妙旅程。我们首先了解了肥皂剧作为文本数据源的特点。接着,我们回顾了从机器学习到深度学习的基本概念,并重点介绍了适用于文本生成的循环神经网络(RNN)的工作原理。


然后,我们对比了Python中三大深度学习框架——Keras、TensorFlow和PyTorch——在构建RNN模型时的不同风格和适用场景。Keras以其高级API和易用性见长,适合快速原型开发;TensorFlow提供强大的底层控制;而PyTorch则以动态图和直观的调试体验吸引研究者。

最后,我们概述了实际生成脚本的流程。选择Keras作为起点,可以帮助初学者以最小的阻力体验深度学习和文本生成的乐趣,并为进一步探索更复杂的模型和框架打下基础。记住,所有的创意技术探索,都始于动手实践。现在,你就可以开始收集你喜欢的剧本文本,尝试生成属于你的第一个“AI肥皂剧”场景了!
002:使用Python构建自动化工作流 🐍

在本教程中,我们将学习如何使用Python构建一个简单的安全自动化系统。我们将从接收警报开始,自动创建工单,并为工单添加丰富的信息,从而减少安全分析师的手动操作。整个过程将基于Webhook和Python的Flask框架实现。

安全自动化基础架构概述
在深入代码之前,了解典型的安全监控团队(如SOC)的基础架构非常重要。系统通常从各种设备(如服务器、网络硬件)收集日志,并将其聚合到集中式系统(如Splunk)中。该系统可以设置警报,当触发时,会创建工单供分析师处理。自动化旨在减少分析师在这些工单间手动复制、粘贴和查询信息的重复性工作。

从Splunk警报到Webhook
上一节我们介绍了基础架构,本节中我们来看看如何从Splunk接收警报。Splunk等日志聚合系统允许你设置基于搜索的警报。当警报触发时,它可以向一个指定的API端点(Webhook)发送HTTP POST请求,其中包含相关的日志数据。
以下是一个简化的Splunk警报配置示例,它每15分钟运行一次,并检查过去15分钟的日志:
search index=firewall src_ip=1.2.3.4
| stats count by src_ip
警报动作可以配置为向一个URL发送Webhook。


构建Webhook接收器
现在,我们需要编写代码来接收Splunk发送的Webhook。我们将使用Python的Flask框架创建一个简单的Web服务器。
首先,我们导入Flask并创建一个应用实例。我们首先设置一个健康检查端点,这对于监控和调试非常有用。

from flask import Flask, request
import json
app = Flask(__name__)
@app.route('/health')
def health_check():
return 'OK', 200

@app.route('/splunk-webhook', methods=['POST'])
def handle_splunk_webhook():
data = request.json
# 暂时将数据写入文件以便查看
with open('webhook_data.json', 'a') as f:
json.dump(data, f)
f.write('\n')
return 'Webhook received', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
运行此脚本后,Splunk的警报触发时,数据将被发送到 /splunk-webhook 端点并保存。
自动创建Jira工单
收到警报数据后,下一步是自动创建工单。我们将使用Jira的Python SDK来连接Jira并创建问题。
我们需要将Jira的配置(如URL、用户名、密码)存储在独立的设置文件中(如settings.py),而不提交到代码库。

以下是创建Jira工单的函数:
from jira import JIRA
import settings
def create_jira_ticket(search_name, results_json):
jira = JIRA(server=settings.JIRA_URL, basic_auth=(settings.JIRA_USER, settings.JIRA_PASS))
issue_dict = {
'project': {'key': 'SEC'},
'summary': f'Alert: {search_name}',
'description': json.dumps(results_json),
'issuetype': {'name': 'Task'},
}
new_issue = jira.create_issue(fields=issue_dict)
return new_issue.key
然后,我们可以在Webhook处理函数中调用此函数,用接收到的数据创建工单。
利用Jira Webhook进行数据丰富
仅仅创建包含原始JSON的工单帮助有限。我们可以利用Jira的Webhook功能,在工单创建后立即触发我们的自动化服务器,进行数据丰富操作。
在Jira中配置一个Webhook,指向我们的自动化服务器(例如 http://your-server/jira-webhook),并设置为在“问题创建”时触发。

以下是处理Jira Webhook并丰富工单信息的代码:
@app.route('/jira-webhook', methods=['POST'])
def handle_jira_webhook():
webhook_data = request.json
issue_key = webhook_data['issue']['key']
# 从工单描述中提取原始数据(之前存入的JSON)
description = webhook_data['issue']['fields']['description']
original_data = json.loads(description)
# 提取感兴趣的信息,例如用户名和文件哈希
username = original_data.get('user')
file_hash = original_data.get('md5')
# 调用丰富函数
enrich_with_ad(username, issue_key)
enrich_with_virustotal(file_hash, issue_key)
return 'Enrichment triggered', 200

实现数据丰富功能
以下是实现两个数据丰富功能的示例:查询Active Directory和查询VirusTotal。

1. Active Directory 查询
import ldap3
def query_active_directory(username):
server = ldap3.Server('ldap://your-ad-server')
conn = ldap3.Connection(server, user='bind_user', password='bind_pass', auto_bind=True)
search_filter = f'(sAMAccountName={username})'
conn.search('dc=example,dc=com', search_filter, attributes=['displayName', 'mail', 'department'])
if conn.entries:
return conn.entries[0]
return None

def enrich_with_ad(username, issue_key):
user_info = query_active_directory(username)
if user_info:
comment = f"AD Lookup for {username}:\nName: {user_info.displayName}\nEmail: {user_info.mail}\nDept: {user_info.department}"
add_jira_comment(issue_key, comment)

2. VirusTotal 查询
import requests
def query_virustotal(file_hash):
url = f'https://www.virustotal.com/vtapi/v2/file/report'
params = {'apikey': settings.VT_API_KEY, 'resource': file_hash}
response = requests.get(url, params=params)
return response.json()
def enrich_with_virustotal(file_hash, issue_key):
vt_result = query_virustotal(file_hash)
positives = vt_result.get('positives', 0)
total = vt_result.get('total', 0)
permalink = vt_result.get('permalink', 'N/A')
comment = f"VirusTotal Scan for {file_hash}:\nDetections: {positives}/{total}\nLink: {permalink}"
add_jira_comment(issue_key, comment)
add_jira_comment 函数使用Jira SDK向指定工单添加评论。
提升代码质量与可维护性
当端点数量增加时,原始的Flask代码会变得难以维护。我们可以使用Flask扩展(如Flask-RESTPlus)或构建自定义框架来统一处理输入验证、错误处理、日志记录和API文档生成。

例如,使用Flask-RESTPlus可以这样定义一个端点:
from flask_restplus import Api, Resource, fields
api = Api(app)
alert_model = api.model('Alert', {
'search_name': fields.String(required=True),
'results': fields.Raw(required=True),
})
@api.route('/splunk-webhook')
class SplunkWebhook(Resource):
@api.expect(alert_model)
def post(self):
data = api.payload
# 处理数据...
return {'message': 'Processed'}, 200
这能自动生成Swagger API文档并进行输入验证。
部署与运维注意事项
在部署自动化系统时,需要考虑可靠性。
- 保持无状态:尽可能设计无状态的服务,避免管理数据库的复杂性。
- 实现重试机制:网络调用可能失败,为关键操作添加重试逻辑。
- 异步处理:将数据丰富等耗时操作设计为异步任务,避免阻塞主流程。可以使用Celery、AWS Lambda或专门的DevOps工具(如StackStorm)。
- 错误处理:确保不会向用户暴露堆栈跟踪等敏感信息,并提供友好的错误消息。
- 利用现有工具:许多DevOps或商业SOAR(安全编排、自动化与响应)平台已经提供了强大的工作流引擎,可以评估是否直接使用或集成。
总结与展望
本节课中我们一起学习了如何使用Python和Flask构建一个基础的安全自动化工作流。我们从接收Splunk Webhook开始,自动创建Jira工单,然后利用Jira的Webhook触发数据丰富流程,为工单添加了Active Directory用户信息和VirusTotal扫描结果。
安全自动化的核心是通过代码和Webhook将不同系统连接起来,消除重复性手动任务。它不是一个替代人的魔法,而是解放分析师和工程师,让他们专注于更高价值的判断性和创造性工作。你可以将这种模式应用于任何以工单为导向的IT或开发流程。


开始构建时,请记住:完成胜过完美。从一个简单的用例开始,快速迭代,并根据反馈不断改进。安全自动化是一个充满机会且回报丰厚的领域。
003:吸引隐形贡献者 🎯


在本节课中,我们将学习如何让开源项目对“隐形贡献者”更具吸引力。这些贡献者可能是有热情但缺乏经验的新手,他们常常因为各种障碍而望而却步。我们将探讨如何通过改善沟通、文档、友善度、术语和行为准则,将项目从“滑动”状态转变为“粘性”状态,从而建立一个更繁荣和包容的社区。
概述与背景
大家好。今天我们将探讨由夏洛特·梅斯分享的关于吸引隐形贡献者的主题。
在开始之前,我想提供一些背景信息。我是本地普拉提小组的共同组织者,因此我花了很多时间与参与者交流,了解他们的期望和目标。我经常听到人们说:“我想要一个能真正参与编码的地方。”同时,作为一个开源项目的核心贡献者,我看到了项目维护者和潜在贡献者之间存在的许多未被察觉的障碍,这些障碍实际上正在驱赶一些人。
那么,我所说的“隐形贡献者”是谁呢?他们通常是学生、自学成才的程序员,或是项目的普通用户。他们可能对开源社区不熟悉,不了解社区默认的假设和流程。当他们查看一个项目时,可能会觉得“这看起来太复杂了”,然后选择离开。
接下来的内容将是一个信息密集的分享。这些见解基于多样化的观点,旨在帮助你理解不同类型的潜在贡献者。如果你是开源项目的维护者或参与者,那么你就是我的目标受众。
面对大量信息时,请不要感到绝望并忽视它。相反,请以适当的节奏进行小的改变。挑选几件可以改进的事情,逐步实施。只要你持续改进,就会不断接近吸引更多贡献者的目标。
如果你想基于这些建议做出改变,请记住,现有贡献者的意见也很重要。如果有人反对,可以询问他们是否有替代方案来解决你希望解决的问题。目标是让现有贡献者和新人都感到受欢迎,共同建设一个繁荣的开源项目。
最后,这些原则不仅适用于开源项目,也可以作为框架,帮助公司吸引更多样化的候选人。在思考如何使公司环境更具包容性时,这些原则同样值得考虑。
核心概念:项目的“粘性”与“滑动”
首先,我将定义两个在本演讲中使用的核心术语:项目的“粘性”与“滑动”。这不是一个非此即彼的二元概念,而是一个光谱。
- 粘性项目:拥有多个长期贡献者。潜在贡献者查看项目时能看到活跃的沟通,即使他们最终没有贡献,也至少会尝试询问:“嘿,我感兴趣,能告诉我更多吗?”
- 滑动项目:可能只有少数长期贡献者(如维护者),偶尔有零星的贡献。几乎没有来自外部查看者的沟通,没有人会说:“嘿,我想帮助这个项目。”
本演讲的目标是帮助你的项目从“滑动”的一端向“粘性”的一端移动。
那么,是什么决定了一个项目在光谱上的位置呢?我将从以下五个方面进行详细阐述:
- 沟通
- 文档
- 友善
- 术语
- 行为准则
上一节我们定义了项目的“粘性”与“滑动”,本节中我们来看看第一个关键因素:沟通。
1. 沟通 💬
你需要确保人们能够轻松地与你的团队沟通。许多项目仅通过邮件列表或 IRC 频道进行沟通,这些方式各有优缺点。
- 邮件列表:有利于存档互动,方便后人查阅历史对话,但不适合实时交流。
- IRC:适合实时交流,但通常缺乏存档,新加入者无法查看之前的对话。
对于新程序员来说,这两种方式都可能带来不便。因此,在考虑沟通渠道时,你需要思考以下几点:
- 显而易见:沟通方式应在每个用户可能访问的地方(如文档首页、GitHub 仓库)明确标出。
- 易于访问:渠道应对非技术人员友好。
- 存档互动:这非常重要,因为它让新人能看到社区的互动风格,判断自己提出“新手问题”时是否会得到友好对待。
- 减少摩擦:尽量使用人们已安装或无需安装即可使用的工具(例如,Slack 有网页版)。这能降低第一次尝试沟通的门槛。
2. 文档 📖
我们都知道文档很重要,但这里需要明确区分用户导向文档和开发者导向文档。
用户文档说明“如何使用项目”,而为了吸引隐形贡献者,你必须提供以开发者为中心的文档。
以下是开发者文档应包含的内容:
- 环境设置:详细说明如何设置开发环境、虚拟环境、运行测试等。应为所有主流操作系统提供步骤。
- PR 提交流程:说明提交 PR 后,多久能得到回复、审核标准是什么。可以链接外部通用指南(如 GitHub 使用教程),但也要说明项目特有的流程。
- 提交后期望:说明测试是否会自动运行、贡献者能否自行查看测试结果并开始修复。
核心原则是:任何你觉得是“常识”或“假设”的事情,都应该记录下来或提供链接。
3. 友善 🤝
友善意味着对所有问题和提交保持礼貌,即使是在拒绝的时候。
项目维护者常会遇到滥用和过高期望,很容易产生不耐烦的情绪。但请记住,隐形贡献者会阅读你所有的回复。如果你对他人尖酸刻薄,新手会担心自己受到同样对待,从而不敢参与。
你可以拒绝而不失友善。 例如:
- 对于不兼容的功能 PR,可以说:“感谢你的工作,但这个功能与项目方向不符。也许你可以考虑分叉并自行维护。”
- 对于代码质量不佳的 PR,可以说:“这不符合我们的代码标准(附上文档链接)。也许你可以先在沟通渠道里找人讨论一下如何改进。”
此外,如果你感到疲惫,不必立即回复。可以稍后处理,或请团队其他成员帮忙。关键是避免留下刻薄、轻蔑的公开记录。
4. 术语 🗣️
术语的使用直接影响新人的第一印象。
- 减少行话:只使用必要的专业术语以确保清晰。不必要的行话会让新人觉得项目高不可攀。
- 避免冒犯性语言:有些术语可能具有冒犯性。例如,“主从”(master/slave)一词历史悠久,但会让部分人感到不适。可以考虑使用“主要/副本”、“领导者/跟随者”等替代词。将这些视为可以逐步解决的技术债务。
- 注意隐含偏见:避免使用如“简单到你妈妈也能做到”这类带有性别或年龄偏见的短语。
- 尊重心理健康:不要滥用“强迫症”(OCD)等描述心理健康状况的词语来形容对代码风格的严格要求。
如果有人指出术语问题,请认真对待。不必立即改变,但应慎重考虑。
5. 行为准则 ⚖️
行为准则是社区健康的基石,但实施起来需要技巧。
- 不要重新发明轮子:直接采用一个成熟、经过审查的行为准则(如贡献者公约),只需进行微调。
- 澄清误解:行为准则不限制你讨论技术内容,而是规范讨论的方式。它要求你在项目官方渠道中保持专业。
- 执行至关重要:仅仅张贴行为准则而不执行是无效的。人们需要看到它被认真对待,才会感到安全。
- 妥善处理违规:第一次处理违规可能会很棘手。关键是要公正,关注具体事件,区分“指控”和“人身攻击”。
- 指控:“B 说的 X 让我不舒服。”——应调查处理。
- 攻击:“B 总是说谎。”——不应容忍。
- 必要时采取行动:如果某人持续制造问题,可能需要禁止其使用沟通渠道,甚至禁止贡献。虽然痛苦,但移除一个骚扰者可能会为多个新贡献者打开大门。
总结与资源
本节课中,我们一起学习了如何通过改善沟通、文档、友善度、术语和行为准则这五个方面,来吸引“隐形贡献者”,使你的开源项目变得更“粘性”。
记住,改变无需一步到位。从小处着手,持续改进。现有贡献者和新人同样重要,目标是建立一个繁荣、包容的社区。
这里有一个包含幻灯片和更多示例材料(如沟通工具对比、文档示例、行为准则链接)的二维码,供你进一步参考。如果你有任何问题,我很乐意在会议期间交流。
感谢大家。

[掌声]
004:最大化使用 IEEE754

概述
在本节课中,我们将要学习计算机中浮点数的表示方式、工作原理以及它们为何有时会表现出奇怪的行为。我们将从整数表示开始,逐步深入到浮点数的核心概念,包括其结构、精度权衡、特殊值(如无穷大和NaN),并探讨如何在实际编程中有效地使用和处理浮点数。
整数与固定小数点的局限
为了理解浮点数,我们需要先了解数字在计算机中是如何表示的。
整数在计算机中的表示相当直接。我们取一块内存,二进制数和日常处理的整数之间存在很好的一对一映射。例如,二进制数 1 对应整数 1,二进制数 101 对应整数 5。
负数则用一种叫做“二的补码”的表示法来表示。这是一种非常巧妙的设计,但本课程主要关注浮点数,因此不深入讨论。
使用32位整数,我们可以表示大约负20亿到正20亿之间的数字。使用64位长整数,则可以表示高达9千万亿的数字。对于大多数实际应用中的整数,这个范围通常是足够的。
然而,当我们开始处理分数数字时,事情就变得困难了。
固定小数点的挑战
想象一下如何实现分数数字。一种方法是取一块内存,将其一分为二:第一部分表示整数部分,第二部分表示小数部分。
例如,如果我们有一个1在小数部分,这代表2的-3次方,即0.125。如果小数部分有一个2,则代表0.25。
但这种表示方式存在明显问题:它能处理的数字范围非常有限。如果我们只使用32位(16位给整数,16位给小数),最小的可表示数字大约是1.5乘以10的负5次方,而最大的数字只有约131,000。
即使我们使用64位,将范围扩大到约40亿,这个系统也完全忽略了负数,并且更重要的是,它无法处理现实世界中常见的极大或极小的数字范围,例如到冥王星的距离(75亿公里)或水分子的大小(三分之一纳米)。
浮点数的诞生:移动的小数点
上一节我们介绍了固定小数点表示法的局限,本节中我们来看看如何解决这个问题。
一个聪明的想法是:我们存储数字本身,同时存储一个“缩放因子”。例如,对于到冥王星的距离7.5,缩放因子是9,这会将7.5的小数点向右移动9位。对于水分子的大小0.33,缩放因子是-9,这会将小数点向左移动9位。
这样,小数点的位置就可以“浮动”了。这,就是浮点数的核心思想。
在计算机内部,一个浮点数由三个不同的部分组成:
- 符号位:控制数字是正数还是负数。
- 指数:即我们刚才讨论的“缩放因子”,决定小数点的位置。
- 小数部分:即数字本身的有效数字部分,有时也称为“尾数”。
浮点数的值通过以下公式计算:
值 = 符号 * (小数部分) * 2^(指数 - 偏置)
这里的“偏置”是为了让指数既能表示正数也能表示负数而引入的一个固定偏移量。这本质上就是二进制的科学计数法。
浮点数的具体例子
为了更好地理解,我们来看几个例子:
-
表示 0.5:
- 0.5 等于 2 的 -1 次方。
- 符号位为 0(正数)。
- 指数部分需要表示 -1。假设偏置为 3,那么存储的指数值应为
-1 + 3 = 2(二进制表示)。 - 小数部分为 1(因为 0.5 = 1 * 2^-1)。
- 所以,
值 = 1 * 2^(2-3) = 2^-1 = 0.5。
-
表示 -88:
- 符号位为 1(负数)。
- -88 可以近似表示为 -11 * 2^3。
- 指数部分存储
3 + 偏置。 - 小数部分存储 11 的二进制近似值。
现代计算机主要使用两种精度的浮点数:
- 32位单精度浮点数:8位指数,23位小数部分。
- 64位双精度浮点数:11位指数,52位小数部分。这是目前大多数编程语言的默认浮点类型。
双精度浮点数可以表示小到小数点后有300多个零的数字,也可以表示大到后面有300多个零的数字,范围非常广。
浮点数的权衡:精度与范围
上一节我们了解了浮点数如何表示极大和极小的数字,本节中我们来看看为此付出的代价——精度权衡。
浮点数的精度是有限的。例如,双精度浮点数大约有15位十进制有效数字的精度。这意味着在这个范围内的数字通常是可靠的。
问题出现在对不同数量级的数字进行加减运算时。一个大数字的“量级”可能会完全掩盖一个小数字的精度。
例如,尝试计算 1e20 + 1。1e20 是1后面跟着20个零。由于双精度浮点数只有约15位有效数字的精度,那个单独的 1 在如此巨大的数字面前变得毫无意义,计算结果仍然是 1e20,仿佛 1 从未被加上。
以下是处理浮点数精度问题的一些要点:
- 乘法和除法通常不受此问题影响。
- 加法与减法需要特别注意数量级。
- 如果需要高精度地求和大量浮点数,不要使用简单的循环相加。
以下是几种求和方法及其特点:
- Python 内置
sum:简单但可能累积舍入误差。 math.fsum:通过跟踪舍入误差来实现高精度求和,但速度较慢。import math # 简单求和可能出错 result_bad = sum([1e100, 1, -1e100]) # 可能得到 0 而不是 1 # 使用 fsum 得到精确结果 result_good = math.fsum([1e100, 1, -1e100]) # 得到 1.0- NumPy 的
sum:采用分块算法,效率高,但精度保证不如fsum。 - 专用库(如
AccuPy):为需要极高精度和性能的数值计算而设计。
无法精确表示的数字
除了精度损失,浮点数还有一个根本性限制:并非所有实数都能被精确表示。
这有些显而易见(如π或e这样的无限不循环小数),但更棘手的是,由于浮点数使用二进制分数,许多简单的十进制分数也无法被精确表示。
最著名的例子就是 0.1。在十进制中,0.1 是 1/10,但在二进制中,它是一个无限循环小数。因此,计算机存储的 0.1 实际上是它最接近的二进制浮点近似值。
为了更好地理解,想象一条代表所有实数的数轴。浮点数就像是数轴上稀疏分布的一些特定点。当一个实数(如0.1)不是这些点之一时,计算机就会选择最接近它的那个浮点数来代表它。
这导致了令人困惑的结果:
0.1 + 0.2 == 0.3 # 在大多数语言中返回 False!
# 因为 0.1 和 0.2 的近似值之和,不等于 0.3 的近似值。
如何应对表示误差
以下是处理浮点数表示误差的实用建议:
- 理解误差必然存在:每次浮点运算都可能引入微小的舍入误差,这些误差会累积。
- 谨慎比较浮点数:永远不要直接用
==比较两个浮点数是否相等。应该检查它们是否“足够接近”。# 错误的方式 if a == b: ... # 正确的方式:检查差值是否小于一个很小的容差(epsilon) epsilon = 1e-10 if abs(a - b) < epsilon: ... # 或者使用库函数 import math if math.isclose(a, b): ... - 显示前务必舍入:这是最重要的一条。在完成所有计算后,根据你的应用场景,将浮点数舍入到有意义的位数再进行显示或存储。这能避免显示出一长串无意义的数字。
浮点数的“特殊居民”:无穷大、负零与NaN
浮点标准(IEEE 754)不仅定义了普通数字,还定义了一些特殊值来处理边界情况。
无穷大
指数位全为1且小数部分全为0表示无穷大(符号位决定正负)。它的行为符合直觉:比任何有限数都大。它常用于:
- 作为寻找最小值算法中的初始最大值。
- 表示溢出(数字太大无法表示)的结果。
- 在某些语言中,1.0 / 0.0 的结果是无穷大(Python会抛出异常)。
负零
浮点数有独立的符号位,因此存在 -0.0。它通常由极小的负数下溢(变得太小而无法表示)产生。在数学上,考虑 1/x 当 x 从负方向趋近于0时,极限是负无穷大。如果 x 下溢为 -0.0,那么 1/(-0.0) 得到 -inf 就保持了连续性。除此之外,-0.0 在比较和大部分运算中与普通 0.0 行为相同。
NaN(非数字)
NaN 代表“不是一个数字”,是浮点数中最有趣的成员。它表示未定义的数学操作结果,例如:
0.0 / 0.0inf / infsqrt(-1)
NaN 具有“传染性”:任何涉及 NaN 的算术运算结果都是 NaN。更独特的是,NaN 是唯一一个不等于自身的值。
x = float(‘nan’)
print(x == x) # 输出 False!
print(x != x) # 输出 True!
因此,检测 NaN 必须使用专门的方法:
import math
math.isnan(x) # 正确方法
# 或者利用其特性
x != x # 如果为 True,则 x 一定是 NaN
NaN 可用作缺失数据或无效数据的占位符(类似于 None),一些库函数(如 np.nanmean)会忽略 NaN 进行计算。
一个有趣的事实是,实际上有 2^52 个不同的 NaN 值(因为小数部分非零即可)。一些语言(如 JavaScript)利用这部分“闲置”空间来高效地存储类型信息。
何时使用替代方案:decimal 模块
我们已经讨论了浮点数的各种问题。如果你需要完全精确的十进制运算(例如处理金融数据),Python 的 decimal 模块是一个更好的选择。
decimal 模块提供十进制浮点运算支持。它仍然有精度限制(默认28位小数),但遵循十进制的舍入规则,避免了二进制表示带来的 0.1 + 0.2 != 0.3 等问题。
from decimal import Decimal
# 使用字符串初始化以避免从浮点数引入初始误差
a = Decimal(‘0.1’)
b = Decimal(‘0.2’)
print(a + b) # 输出 Decimal(‘0.3’)
print(a + b == Decimal(‘0.3’)) # 输出 True
然而,这种精确性是有代价的:
- 速度慢:
Decimal运算比浮点运算慢得多。 - 内存可变:
Decimal对象占用的内存随数字大小和精度增长,而浮点数(如float)是固定大小的。
因此,decimal 适用于对精度要求极高且性能不是首要关键的场景(如财务计算),而 float 适用于科学计算、图形处理等需要大量运算且能容忍微小误差的场景。
总结
本节课中我们一起深入探讨了浮点数。
我们首先回顾了整数和固定小数点表示法的局限,从而引出了浮点数通过符号位、指数和小数部分来动态表示极大和极小数字的核心设计。
我们认识到浮点数在获得巨大表示范围的同时,也面临着精度权衡和无法精确表示所有实数的根本限制,这导致了在加减运算和比较时需要特别小心。
我们还了解了浮点数家族中的特殊成员:无穷大、负零和NaN,并学习了它们的用途和检测方法。
最后,我们知道了对于需要精确十进制运算的场景,可以求助于 decimal 模块,但需要接受其性能上的代价。

希望本课程能帮助你理解浮点数,让它们从令人困惑的“敌人”,变成你编程工具箱中可被理解和有效利用的“朋友”。
005:解决真实世界的生产问题 🚀

在本教程中,我们将跟随 Lynn Root 在 PyCon 2019 的演讲,学习如何在实际生产环境中应用 Python 的异步 I/O (asyncio)。我们将构建一个名为“混沌曼德尔”的服务,它监听发布/订阅消息并重启主机,以此为例,探讨优雅关闭、异常处理、多线程、测试、调试和性能分析等高级主题。
1️⃣:基础概念与项目初始化
异步 I/O 是 Python 中实现并发编程的强大工具。它提供了不同层次的抽象,让开发者可以根据需求进行精细控制。一个简单的“Hello, World”示例虽然容易上手,但容易让人产生虚假的安全感。真实世界的服务(如处理 HTTP 请求、响应发布/订阅事件、管理指标等)要复杂得多。
我们将构建一个服务来模拟“混沌猴子”,它监听消息并重启主机。首先,我们从编写基础代码开始。
发布者协程
以下是发布者协程的代码。它在一个无限循环中生成带有唯一 ID 的消息,并将其发布到队列中。
import asyncio
import uuid
async def publisher(queue):
while True:
message_id = str(uuid.uuid4())
# 使用 create_task 来调度协程,避免阻塞循环
task = asyncio.create_task(queue.put(message_id))
# 这里没有 await task,实现了“发射即忘”
关键点:我们使用 asyncio.create_task 来调度 queue.put 协程,而不是直接使用 await。这样做可以避免阻塞 while 循环的后续迭代,允许事件循环继续处理其他任务。如果队列有大小限制,我们可能需要等待空间,但此例中我们继续使用 create_task。
消费者协程
现在,我们需要一个消费者来接收并处理这些消息。
async def consumer(queue):
while True:
# 等待队列中的消息,这里使用 await 是合理的,因为消费者在没有消息时无事可做
message = await queue.get()
# 处理消息(后续会填充具体逻辑)
print(f"Consumed: {message}")
关键点:消费者在 queue.get() 上使用 await。这只会阻塞消费者协程本身,而不会阻塞整个事件循环或其他任务。这是处理 I/O 等待的典型模式。
2️⃣:处理消息与任务编排
上一节我们介绍了生产者和消费者的基础结构。本节中,我们来看看如何处理具体的消息,并协调多个可能并发的任务。
当我们消费到一条消息时,可能需要执行多个操作,例如重启主机和将消息保存到数据库。这两个操作可能彼此独立,可以并发执行。
独立任务处理
我们将消息处理逻辑封装到一个单独的协程中,并使用 asyncio.create_task 来并发执行重启和保存操作。
async def process_message(message):
# 创建重启主机的任务
restart_task = asyncio.create_task(restart_host(message))
# 创建保存消息的任务
save_task = asyncio.create_task(save_message(message))
# 使用 asyncio.gather 等待两个任务都完成
await asyncio.gather(restart_task, save_task)
# 两个任务完成后,确认消息,防止重新投递
await acknowledge_message(message)
async def restart_host(message):
# 模拟重启主机的 I/O 操作
await asyncio.sleep(1)
print(f"Host restarted for message: {message}")
async def save_message(message):
# 模拟保存消息到数据库的 I/O 操作
await asyncio.sleep(0.5)
print(f"Message saved: {message}")
关键点:asyncio.gather 用于并发运行多个协程,并等待它们全部完成。它返回一个结果列表(顺序与传入的协程顺序一致)。这确保了在确认消息之前,重启和保存操作都已完成。
顺序性依赖任务
有时,任务之间存在依赖关系,必须按顺序执行。例如,你可能需要先检查主机正常运行时间,然后再决定是否重启。
async def process_message_sequential(message):
# 必须先检查
uptime = await check_uptime(message)
if uptime > 7 * 24 * 3600: # 超过7天
# 然后重启
await restart_host(message)
await save_message(message)
关键点:即使代码在逻辑上是顺序的(先 A 后 B),它仍然是异步的。await check_uptime 会让出控制权给事件循环,允许其他任务运行,但 restart_host 只会在 check_uptime 完成后才执行。
3️⃣:优雅关闭服务
我们的服务需要能够优雅地关闭,例如在收到终止信号时,清理数据库连接、停止消费消息、完成现有请求等。
信号处理
我们不能仅仅依赖 try...except KeyboardInterrupt,因为程序可能通过其他信号(如 SIGTERM)终止。我们应该为事件循环设置信号处理程序。
以下是设置优雅关闭的模板代码:
import asyncio
import signal
async def shutdown(signal_name, loop, tasks):
"""执行关闭任务"""
print(f"Received exit signal {signal_name}...")
# 取消所有未完成的任务(除了当前关闭任务本身)
for task in [t for t in tasks if t is not asyncio.current_task()]:
task.cancel()
# 等待任务取消
await asyncio.gather(*tasks, return_exceptions=True)
# 停止事件循环
loop.stop()
def main():
loop = asyncio.get_event_loop()
# 创建任务列表以便后续取消
tasks = [asyncio.create_task(publisher(queue)), asyncio.create_task(consumer(queue))]
# 为 SIGINT 和 SIGTERM 设置信号处理程序
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(
sig,
lambda s=sig: asyncio.create_task(shutdown(s.name, loop, tasks))
)
try:
loop.run_forever()
finally:
loop.close()
print("Shutdown complete.")
关键点:
loop.add_signal_handler用于注册信号处理程序。- 处理程序内部创建一个
shutdown协程任务来执行清理。 - 我们收集并取消所有运行中的任务(使用
return_exceptions=True防止未处理异常导致崩溃)。 finally块确保循环最终被关闭。
关于 asyncio.shield 的警告:文档中提到 asyncio.shield 可以保护任务不被取消,但在实际关闭场景中,它可能无法按预期工作,因为被保护的任务仍然包含在 asyncio.all_tasks() 中并会被取消。需要谨慎使用。
4️⃣:异常处理
异步代码中的异常如果不被正确处理,可能会被静默吞没,导致难以调试的问题。
全局异常处理器
我们可以为事件循环设置一个全局默认异常处理器。
def handle_exception(loop, context):
"""全局异常处理器"""
msg = context.get("exception", context["message"])
print(f"Caught global exception: {msg}")
# 这里可以记录日志、发送警报等
loop = asyncio.get_event_loop()
loop.set_exception_handler(handle_exception)
任务级异常处理
对于 asyncio.gather,我们可以通过 return_exceptions=True 参数来获取任务中的异常作为结果,而不是让异常立即抛出。
async def process_message_safe(message):
restart_task = asyncio.create_task(restart_host(message))
save_task = asyncio.create_task(save_message(message))
# 使用 return_exceptions=True 来收集异常
results = await asyncio.gather(restart_task, save_task, return_exceptions=True)
for i, result in enumerate(results):
if isinstance(result, Exception):
print(f"Task {i} failed with: {result}")
# 根据具体异常类型进行特定处理,例如重试或拒绝消息
else:
print(f"Task {i} succeeded with: {result}")
关键点:务必设置某种形式的异常处理(全局或任务级),否则异常可能导致未定义行为或被忽略。
5️⃣:与多线程代码协作
有时你不得不与同步(阻塞)代码或线程交互,例如使用一个基于线程的发布/订阅客户端。
在线程中调度协程
如果从另一个线程需要调度协程到主事件循环,必须使用线程安全的 API。
import asyncio
from concurrent.futures import ThreadPoolExecutor
def blocking_io_operation():
# 这是一个阻塞的 I/O 函数
time.sleep(2)
return "Data from blocking IO"
async def main_in_async_world():
loop = asyncio.get_event_loop()
# 在线程池中运行阻塞函数
executor = ThreadPoolExecutor()
blocking_data = await loop.run_in_executor(executor, blocking_io_operation)
print(blocking_data)
# 如果要从线程回调中调度新协程,必须使用 `call_soon_threadsafe` 或 `run_coroutine_threadsafe`
def callback_from_thread():
future = asyncio.run_coroutine_threadsafe(async_task(), loop)
# 可以 future.result() 等待结果,但会阻塞线程
关键点:
- 使用
loop.run_in_executor将阻塞调用转移到线程池,避免阻塞事件循环。 - 要从非主线程调度协程,必须使用
asyncio.run_coroutine_threadsafe或loop.call_soon_threadsafe,直接调用loop.create_task不是线程安全的。
6️⃣:测试异步代码
测试异步代码需要特殊的工具和方法,因为测试函数本身需要在事件循环中运行。
使用 pytest-asyncio 插件
pytest-asyncio 插件简化了异步测试的编写。
import pytest
import asyncio
@pytest.mark.asyncio
async def test_save_coroutine():
"""测试一个异步函数"""
result = await save_message("test_msg")
assert result is None # 根据实际函数定义断言
关键点:使用 @pytest.mark.asyncio 装饰器,并将测试函数本身定义为 async。插件会自动管理事件循环。
模拟异步函数(Mocking)
模拟(Mocking)异步函数和协程需要一些技巧,因为标准的 unittest.mock 对 async 支持有限。
以下是一个使用 pytest 夹具来模拟协程的示例:
import pytest
from unittest.mock import Mock, AsyncMock, patch
@pytest.fixture
def mock_sleep():
"""创建一个模拟的 asyncio.sleep"""
with patch('mayhem.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
yield mock_sleep
@pytest.mark.asyncio
async def test_save_with_mock(mock_sleep):
"""使用模拟的 sleep 测试 save_message"""
mock_sleep.return_value = None
await save_message("test")
mock_sleep.assert_awaited_once_with(0.5) # 使用 assert_awaited_* 用于 AsyncMock
关键点:
- 对于 Python 3.8+,可以使用
unittest.mock.AsyncMock。 - 使用
pytest的patch夹具来替换目标协程。 - 对
AsyncMock的断言需要使用assert_awaited_once_with等方法。
测试调用了 create_task 的代码
测试创建了任务的函数时,需要确保任务被实际执行。仅仅调度任务(create_task)可能不足以在测试中推进它们。
@pytest.mark.asyncio
async def test_consumer_schedules_task():
mock_queue = AsyncMock()
mock_queue.get.side_effect = ["msg1", asyncio.CancelledError()] # 发送一个消息然后取消
mock_process = AsyncMock()
with patch('mayhem.process_message', mock_process):
consumer_task = asyncio.create_task(consumer(mock_queue))
# 需要给事件循环一个机会来运行调度的任务
await asyncio.sleep(0.01)
consumer_task.cancel()
try:
await consumer_task
except asyncio.CancelledError:
pass
mock_process.assert_awaited_once_with("msg1")
关键点:在测试中,可能需要手动 await asyncio.sleep(0) 或一小段时间,以让事件循环处理已调度的任务。
对于更复杂的场景,可以考虑使用 asyncio 标准库中的 asynctest 模块,它提供了更强大的测试工具。
7️⃣:调试异步代码
调试异步程序有其独特的挑战,比如跟踪多个并发任务的状态。
使用内置调试模式
asyncio 有一个内置的调试模式,可以提供丰富的信息。
import asyncio
import logging
# 启用调试模式
logging.basicConfig(level=logging.DEBUG)
asyncio.run(main(), debug=True)
调试模式提供:
- 未处理异常的详细信息:包括异常发生时的任务和源跟踪。
- 线程安全警告:如果检测到非线程安全的操作,会抛出
RuntimeError。 - 慢回调日志:默认记录执行时间超过 100ms 的协程,帮助识别性能瓶颈。
打印任务堆栈
对于临时调试,可以打印运行中任务的堆栈信息。
for task in asyncio.all_tasks():
print(task)
task.print_stack(limit=5)
生产环境下的轻量级调试
在生产环境中启用完全调试模式可能开销太大。可以使用 aiodebug 这样的轻量级库,它专注于记录慢回调和执行时间。
import aiodebug
aiodebug.log_slow_callbacks.enable(0.05) # 记录超过50ms的回调
8️⃣:性能分析
分析异步代码的性能可能与同步代码略有不同,因为时间花费在多个交错的任务上。
使用 cProfile 和可视化工具
-
使用 cProfile 收集数据:
python -m cProfile -o output.prof your_script.py -
使用 snakeviz 进行可视化:
pip install snakeviz snakeviz output.profsnakeviz会提供一个交互式的火焰图,帮助你直观地看到时间消耗在哪里。
使用 py-spy 进行实时分析
py-spy 是一个采样分析器,可以连接到正在运行的进程,对生产环境的影响很小。
pip install py-spy
py-spy top --pid <PID> # 查看实时统计
py-spy record -o profile.svg --pid <PID> # 记录并生成火焰图
使用 line_profiler 进行逐行分析
当你怀疑某个特定函数是热点时,可以使用 line_profiler 进行更细粒度的分析。
-
装饰目标函数:
from line_profiler import profile @profile async def save_message(message): # ... 函数体 -
使用 kernprof 运行脚本:
kernprof -l -v your_script.py它会输出函数中每一行代码的执行时间和次数。
针对异步的优化发现
在分析过程中,你可能会发现一些意想不到的瓶颈。例如,Lynn 在分析中发现大量的时间花在了标准库的 logging 模块上,这本身是阻塞的。
解决方案:使用 aiologger 这样的异步日志记录库。

import asyncio
from aiologger import Logger


async def main():
logger = Logger.with_default_handlers()
await logger.info("This is a non-blocking log message!")
await logger.shutdown()


替换为异步日志记录器后,相关函数的执行时间显著减少。
总结 📝
在本教程中,我们一起学习了如何在实际生产项目中应用高级 asyncio 技术:
- 基础与编排:理解了
create_task和gather的用法,学会了如何编排独立或具有依赖关系的并发任务。 - 优雅关闭:学会了通过信号处理程序实现服务的优雅关闭,确保资源被正确清理。
- 异常处理:掌握了设置全局和任务级异常处理器的方法,避免异常被静默吞没。
- 多线程协作:了解了如何安全地在异步事件循环和线程之间进行交互。
- 测试:使用
pytest-asyncio和 mocking 技术来有效地测试异步代码。 - 调试:利用
asyncio的调试模式、任务堆栈打印和aiodebug等工具来定位问题。 - 性能分析:通过
cProfile/snakeviz、py-spy和line_profiler等工具链,识别并优化性能瓶颈。

记住,使用 asyncio 不仅仅是添加 async/await 关键字,它需要一种思维模式的转变,仔细考虑哪些操作可以并发,哪些必须顺序执行,并妥善处理并发带来的复杂性。希望这个基于真实场景的教程能帮助你构建更健壮、高效的异步 Python 服务。
006:Andy Fundinger - 在点上发生的8件事


概述
在本教程中,我们将深入探讨Python中一个强大但常被忽视的特性:属性访问机制与描述符协议。我们将学习当你在对象上使用点号(.)访问或设置属性时,Python解释器在幕后执行的八个关键步骤。理解这些机制对于编写高级、灵活且高效的Python代码至关重要。
属性访问的八个步骤 🔍
上一节我们概述了本教程的目标,本节中我们来看看属性访问过程中具体发生的八件事。这八件事共同构成了Python对象属性查找和操作的完整链条。
以下是属性访问时发生的八个步骤:
- 实例字典查找:首先,Python会检查对象的
__dict__中是否存在该属性。 - 类字典查找:如果实例字典中没有,则查找其所属类的
__dict__。 - 基类字典查找:如果类字典中也没有,则按照方法解析顺序(MRO)在其基类字典中查找。
- 调用
__getattr__:如果以上都未找到,且类定义了__getattr__方法,则调用它。 - 调用
__getattribute__:实际上,__getattribute__方法管理着整个属性访问流程,它总是被最先调用。 - 调用描述符的
__get__:如果在类字典中找到的是一个定义了__get__方法的对象(描述符),则调用它。 - 调用描述符的
__set__:当为属性赋值时,如果类字典中的对象定义了__set__方法,则调用它。 - 引发
AttributeError:如果所有查找路径都失败,__getattribute__最终会引发AttributeError异常。

基础属性访问 📖

上一节我们列出了属性访问的八个步骤,本节中我们来看看最基础、最直接的几种属性查找方式。
来自实例字典
最简单的属性访问直接从对象的 __dict__ 中获取值。
class MyObject:
pass

o = MyObject()
o.__dict__[‘x‘] = 3
print(o.x) # 输出: 3
来自类字典

如果实例字典中没有该属性,Python会到其所属类的字典中查找。
class MyObject:
y = 2
o = MyObject()
print(o.y) # 输出: 2
来自父类(基类)字典

如果当前类字典中也没有,查找会沿着继承链向上进行。
class Base:
t = 4
class MyObject(Base):
pass
o = MyObject()
print(o.t) # 输出: 4
特殊方法拦截 🛡️
上一节我们了解了基础的字典查找,本节中我们来看看如何通过特殊方法更精细地控制属性访问。
__getattr__ 方法
当在实例和类的字典中都找不到属性时,如果定义了 __getattr__,它会被调用。
class Probe:
def __getattr__(self, name):
print(f‘__getattr__ called for {name}‘)
return ‘RetVal‘
p = Probe()
print(p.r) # 触发 __getattr__,输出: RetVal
p.r = 5 # 设置值后,__getattr__ 不再被调用
print(p.r) # 直接输出: 5
__setattr__ 方法
当给实例属性赋值时,__setattr__ 方法会被调用。

class Probe:
def __setattr__(self, name, value):
print(f‘Setting {name} to {value}‘)
# 注意:如果不调用父类方法,属性不会被真正设置
super().__setattr__(name, value)
p = Probe()
p.t = 3 # 输出: Setting t to 3
__getattribute__ 方法
__getattribute__ 是属性访问的总入口,每次属性访问都会触发它,优先级最高。

class Probe:
def __getattribute__(self, name):
print(f‘__getattribute__ called for {name}‘)
return ‘Anything‘
p = Probe()
print(p.any_var) # 输出: Anything
p.any_var = 10
print(p.any_var) # 仍然输出: Anything,因为 __getattribute__ 完全接管了访问
描述符协议 ⚙️
上一节我们介绍了通过特殊方法进行拦截,本节中我们来看看Python属性系统的核心机制:描述符协议。
描述符是实现了特定协议(__get__, __set__, __delete__)的类。它们允许你自定义属性在访问、设置和删除时的行为。
非数据描述符

只定义了 __get__ 方法的描述符称为非数据描述符。
class NonDataDesc:
def __get__(self, obj, objtype=None):
print(‘NonDataDesc.__get__ called‘)
return ‘A value from get‘
class MyClass:
z = NonDataDesc() # 描述符作为类属性
o = MyClass()
print(o.z) # 触发 __get__,输出: A value from get
o.z = None # 在实例字典中设置值
print(o.z) # 输出: None,实例字典优先级高于非数据描述符

数据描述符
定义了 __get__ 和 __set__(或 __delete__)方法的描述符称为数据描述符。数据描述符的优先级高于实例字典。
class DataDesc:
def __get__(self, obj, objtype=None):
print(‘DataDesc.__get__ called‘)
return ‘Value from DataDesc get‘
def __set__(self, obj, value):
print(f‘DataDesc.__set__ called with {value}‘)

class MyClass:
z = DataDesc()
o = MyClass()
o.z = 100 # 触发 __set__,输出: DataDesc.__set__ called with 100
print(o.z) # 触发 __get__,输出: Value from DataDesc get
# 尽管没有真正存储值,但 __get__ 仍被调用
print(o.__dict__) # 输出: {},实例字典为空
描述符的签名
以下是描述符方法的典型签名:
__get__(self, obj, objtype=None) -> objectobj: 调用描述符的实例。如果从类访问,则为None。objtype: 所有者类。
__set__(self, obj, value) -> Noneobj: 实例。value: 要设置的值。
__delete__(self, obj) -> Noneobj: 实例。
__set_name__(self, owner, name)(Python 3.6+)- 在类创建时调用,告知描述符它被赋予的名称。

描述符的实际应用 🛠️

上一节我们学习了描述符协议的理论,本节中我们来看看描述符在现实世界中的强大应用。

描述符是许多Python高级特性的基石。
方法绑定
实例方法之所以能自动绑定 self 参数,正是利用了非数据描述符。
class Greeter:
def greeting(self):
print(‘Hi‘)
print(Greeter.greeting) # 输出: <function Greeter.greeting at ...>
g = Greeter()
print(g.greeting) # 输出: <bound method Greeter.greeting of <__main__.Greeter object at ...>>
g.greeting() # 输出: Hi
函数对象本身就是一个非数据描述符,其 __get__ 方法返回一个绑定了实例的 method 对象。
属性别名
以下是一个数据描述符的实用例子:为属性创建别名。
class Alias:
"""一个属性别名描述符"""
def __init__(self, name):
self.name = name # 实际存储数据的属性名
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.name)
def __set__(self, obj, value):
setattr(obj, self.name, value)
class DataClass:
data_x = Alias(‘legacy_x‘)
data_y = Alias(‘legacy_y‘)
data_z = Alias(‘legacy_z‘)
def __init__(self, x, y, z):
self.legacy_x = x
self.legacy_y = y
self.legacy_z = z

obj = DataClass(‘high‘, ‘pycon‘, 2019)
print(obj.data_x, obj.legacy_x) # 输出: high high
obj.data_x = ‘higher‘
print(obj.legacy_x) # 输出: higher
带默认实例的方法调用

这是一个非数据描述符的例子,它允许在类级别调用实例方法时,自动创建一个默认实例。
from functools import partial
class DefaultMethod:
"""一个装饰器/描述符,允许在类级别调用实例方法"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
# 如果在类级别调用,创建一个默认实例
print(‘Creating default instance‘)
obj = objtype() # 调用无参 __init__
return partial(self.func, obj) # 绑定实例
return partial(self.func, obj) # 绑定实例
class NamePrinter:
def __init__(self, name=‘Default‘):
self.name = name
@DefaultMethod
def name_print(self):
print(f‘Name: {self.name}‘)
def print_name(self):
print(f‘Name: {self.name}‘)
andy = NamePrinter(‘Andy‘)
andy.name_print() # 输出: Name: Andy
andy.print_name() # 输出: Name: Andy
NamePrinter.name_print() # 输出: Creating default instance \n Name: Default
# NamePrinter.print_name() # 会引发 TypeError
总结与建议 📝
本节课中我们一起学习了Python属性访问的完整流程和强大的描述符协议。
我们了解到,一个简单的点号操作背后,Python解释器可能执行多达八个步骤的查找和调用。描述符协议(__get__, __set__, __delete__)是Python实现属性管理、方法绑定、@property、@classmethod、@staticmethod等特性的核心机制。
何时使用描述符?

描述符是一种高级的“元编程”工具,就像一把强大的铲子。
- 不要过早使用:如果你的问题可以用简单的属性或方法解决,就不要使用描述符。过早引入会增加代码的复杂性。
- 在需要时使用:当你需要跨多个属性实现一致的复杂行为(如类型验证、惰性求值、数据持久化到数据库、属性别名、监控等),并且收益大于维护成本时,描述符是绝佳的选择。

记住,强大的能力意味着更大的责任。理解这些底层机制能让你更好地驾驭Python,写出更优雅、高效的代码。
007:抓取百万场宝可梦战斗 🎮


在本教程中,我们将跟随 Duy Nguyen 的演讲,学习如何构建一个分布式系统来抓取和分析数百万场在线宝可梦对战。我们将从分析一个简单的单体应用开始,逐步将其重构为可扩展、高可用的分布式架构,并探讨在云环境中处理并发、测试和系统弹性的核心概念。


概述
本节课我们将学习如何将一个简单的网页抓取脚本,演进为一个能够处理海量数据的分布式系统。我们将重点关注三个核心环节:通过并发实现扩展、确保系统正确性以及构建故障恢复能力。我们将以宝可梦对战数据抓取项目为例,具体讲解如何分析系统特性、划分组件边界,并运用现代云服务和设计模式。
分布式系统入门:1:项目背景与动机
大家好,我是 Duy Nguyen。我目前是谷歌的一名软件工程师。今天我想分享一个我在环游世界期间完成的个人项目:抓取和分析在线宝可梦对战平台上的所有公开战斗。
我热爱经典的宝可梦掌机游戏。网络上有一个名为“Pokemon Showdown”的网站,玩家可以在此进行在线对战。然而,我发现这个社区对新玩家并不友好,高级玩家的对战策略往往被刻意隐藏。这使得新手难以学习和进步。
我认为这些信息应该被民主化。有一天,我注意到该网站的所有对战记录都可以公开查看,无需认证。于是,我萌生了一个想法:抓取并分析所有的对战数据。这个项目后来成为了我理解分布式系统的一个绝佳案例。

分布式系统入门:2:从单体应用到分布式架构
最初,我写了一个简单的 Python 脚本。它的工作流程非常直接:
- 监控网站的主聊天室,获取最近的对战列表。
- 遍历列表中的每个对战链接。
- 依次访问每个链接,等待对战结束,然后从网页中抓取对战日志。
代码结构类似于这样:
# 简化的初始版本
def monitor_room():
battle_urls = get_recent_battle_urls() # 获取URL列表
for url in battle_urls:
download_battle_log(url) # 顺序下载每个对战
这个方案对于最小可行产品(MVP)来说足够了,但我很快遇到了问题。随着想法的增多,我需要在脑海中同时维护整个应用的逻辑,这变得非常困难。此外,下载对战的速度远远跟不上我发现新对战的速度。
上一节我们介绍了项目的初衷,本节中我们来看看系统演进的第一步。我们需要分析系统的不同部分,找出可以改进的地方。
以下是初始方案面临的核心挑战:
- 认知负载过高:应用逻辑变得复杂,难以在脑中整体把握。
- 吞吐量瓶颈:不同组件的工作速率差异巨大。
- 监控器:获取对战列表很快(约300毫秒)。
- 下载器:下载一场对战的时间从2秒到45分钟不等,变化极大。
- 职责混杂:所有逻辑(监控、下载)和状态(URL列表)都耦合在一个程序中。
这些差异(职责、工作速率)表明,是时候将应用程序分解为更小的、专注的组件了。这个过程通常被称为 “划分边界” 或 “分解单体应用”。
分布式系统入门:3:引入并发与消息队列
分析了系统特性后,我们可以进行责任分区。我们将应用拆分为两个主要组件和一个协调中心:
- 生产者:负责监控网站并发现新的对战URL。
- 消费者:负责下载具体的对战日志。
- 消息队列:作为中间协调者,传递URL工作项。
这种“生产者-消费者”模式可以通过并发来提升吞吐量。在单机多线程环境中,我们可以使用Python的 queue.Queue 作为线程安全的队列。
计算模型如下:
生产者线程 -> (放入URL) -> [线程安全队列] -> (取出URL) -> 消费者线程
通过增加消费者(下载器)线程的数量,我们可以近乎线性地提升下载吞吐量。Raymond Hettinger 在 PyCon 2016 Russia 的演讲中强烈建议,在大多数应用场景下,应优先使用像线程安全队列这样的高级并发原语,而不是直接操作锁等底层机制。
然而,这个方案仍有一个致命弱点:状态(队列)存在于内存中。如果进程崩溃,队列中所有待处理的工作都会丢失。这对于需要可靠性的系统是不可接受的。
幸运的是,由于我们已经对组件进行了清晰的解耦,替换中间的状态存储变得非常容易。我们可以将内存队列替换为云上的托管消息队列服务,例如 Amazon SQS。
这一替换带来了巨大好处:
- 消息持久化:即使我们的应用崩溃,任务也不会丢失。
- 弹性扩展:SQS可以处理海量消息。
- 监控与管理:云服务提供了开箱即用的监控工具。
这个案例表明,通过先分析系统、划分边界,我们可以让核心业务逻辑与具体的实现细节(如使用哪种队列)解耦,从而灵活地利用云服务的强大功能。
分布式系统入门:4:设计模式的普适性
“生产者-消费者”通过“消息队列”通信的模式是一个通用设计模式。有趣的是,不同的技术栈会不约而同地采用相似的解决方案。
以 Go 语言为例,其并发模型是语言的一等公民。要实现同样的功能:
- 生产者和消费者可以分别由
goroutine(轻量级线程)实现。 - 它们之间的通信则通过
channel(通道)进行。
代码如下所示:
// Go语言示例(概念性)
go monitorRoom(battleChan) // 启动生产者goroutine
go downloadBattle(battleChan) // 启动消费者goroutine
// monitorRoom 向 battleChan 发送URL
// downloadBattle 从 battleChan 接收URL
无论是 Python 的多线程+Queue、AWS 的微服务+SQS,还是 Go 的 goroutine+channel,其核心架构都是相通的。这告诉我们,在分布式系统设计中,关注组件如何组合与协调,比纠结于特定语言或工具的细节更为重要。
分布式系统入门:5:测试分布式系统
引入并发和外部服务(如SQS)后,系统变得难以预测和测试。我们失去了单体程序的确定性。例如,任务的执行顺序可能错乱,启动外部依赖可能需要很长时间,网络分区和外部服务不可用成为新的故障点。
核心问题变为:如何测试这些外部依赖?
通常我们有几种测试替身(Test Double)策略:
- Mock:模拟对象,用于记录和验证交互。它轻量、快速,但不实现真实逻辑。
- Fake:伪造对象,实现与真实服务相同的接口,并保证关键不变性(如FIFO顺序),但采用简化实现(如内存存储)。
- 真实服务:直接测试真实的外部服务(如启动一个测试用的SQS队列)。
不同的场景有不同的选择:
- 在本项目中:我选择了使用 Fake。因为当时我没有稳定收入,需要严格控制AWS开销。Fake 能提供足够的真实感,避免产生云服务费用,虽然需要我投入更多开发时间来实现它。
- 在谷歌:倾向于使用 真实服务。因为谷歌拥有庞大的基础设施,可以快速、低成本地提供测试所需的真实资源。其首要目标是节省工程师的调试和开发时间。
- 对Python社区的建议:我建议从 Mock 开始。Mock 最简单、最易学,能让新开发者快速上手。遵循“先做最简单的事”的原则,在大多数情况下,Mock 足以验证逻辑的正确性。
总结来说,选择哪种策略取决于你在现实性、开发成本和执行速度之间的权衡。

分布式系统入门:6:构建系统弹性

分布式系统总会失败。当我们的系统规模扩大后,故障会从偶然变为必然。因此,我们必须设计弹性策略来应对失败。
.NET 的 Polly 库以一种清晰的方式对弹性策略进行了分类。在我的项目中,我主要应用了以下几种策略:
1. 超时
对于下载对战这个可能耗时很长的操作,必须设置超时。我们不能无限期等待。
# 等待页面上的某个元素出现,最多等待45分钟
element = WebDriverWait(driver, 45*60).until(
EC.element_to_be_clickable((By.ID, “download-button“))
)
2. 重试
许多故障是暂时的,例如网络闪断、服务器过载。对于这类错误,重试是有效的策略。
以下是项目中针对不同错误的重试逻辑:
- 连接丢失:服务器断开连接,重试。
- WebDriver错误:浏览器驱动进入坏状态,重试。
- 战斗未结束:检查时对战还未完成,重试。
3. 后备与放弃
当重试耗尽后,我们需要一个后备方案。在我的代码中,如果最终失败,则返回一个空列表(一个“零值”对象)。
更激进但有效的策略是 “放弃”。如果某个工作单元(如一个下载器)陷入无法恢复的坏状态,最直接的办法是销毁它并创建一个全新的实例。这借鉴了云环境中“不可变基础设施”的思想——与其修复,不如替换。
# 概念性代码:如果重试多次后仍失败,则放弃并重建下载器
if retries_exhausted:
return [] # 后备:返回空结果
# 同时,在外层逻辑中,可以考虑销毁并重启这个下载器进程
通过组合使用超时、重试和后备/放弃策略,我们为系统注入了应对故障的弹性,使其能够从各种预期内的失败中自动恢复。
总结
本节课中我们一起学习了如何将一个简单的想法逐步构建成健壮的分布式系统。我们经历了以下关键步骤:
- 分析系统特性:识别出不同组件在职责和工作速率上的差异,为分解系统提供依据。
- 划分边界与解耦:将单体应用拆分为生产者、消费者和消息队列,使各组件职责单一。
- 引入并发与云服务:采用“生产者-消费者”模式提升吞吐量,并用云消息队列(如SQS)替换内存队列,获得持久化和可扩展性。
- 保障正确性:探讨了使用Mock、Fake或真实服务来测试外部依赖的策略,并根据不同场景做出选择。
- 构建弹性:通过实施超时、重试和后备(放弃)策略,使系统能够从容应对网络波动、服务中断等不可避免的故障。

最终,我们不仅成功抓取了海量宝可梦对战数据,更重要的是,通过这个项目实践了分布式系统设计的核心思想:通过解耦和组合来管理复杂性,并始终为失败做好准备。记住,我们不只是为计算机编写代码,更是为了解决人的问题。希望这个案例能帮助你在未来设计系统时,找到更清晰、更稳健的架构思路。
008:统计性能分析与sys模块



在本节课中,我们将要学习Python性能分析的核心概念,特别是统计性能分析(Statistical Profiling)的原理、优势以及如何利用sys模块中的强大工具来实现它。我们还将对比确定性分析(Deterministic Profiling),并了解如何编写自己的、适用于生产环境的低开销分析器。
什么是性能分析?📊
根据Python官方定义,性能分析(Profiling)是一组统计数据,用于描述程序的各个部分执行的频率和持续时间。性能分析的主要目的是找出程序中运行缓慢的部分,以便开发者可以集中精力优化这些部分,编写出更高效的代码。
在Python中,程序的各个部分(如函数)会映射到这些统计数据中。

确定性性能分析 🛠️
上一节我们介绍了性能分析的基本概念,本节中我们来看看一种常见的分析类型:确定性性能分析。
确定性分析会精确记录每一个函数调用的开始和结束时间。Python内置的cProfile模块就是一个易于使用的确定性分析器。
以下是使用cProfile的基本方法:
import cProfile
cProfile.run(‘your_function()‘)
运行代码后,cProfile会返回一份分析报告,其中包含:
- 函数调用次数
- 每个函数的调用频率
- 总时间、每次调用平均时间、累积时间
- 按耗时排序的函数列表
通过查看这份报告,你可以清晰地识别出程序中哪些部分运行缓慢或耗时超出预期,从而进行针对性优化。


确定性分析的工作原理

确定性分析之所以能精确追踪,是因为sys模块提供了强大的钩子(hooks)。
- 调用
sys.setprofile()可以设置一个分析函数。此后,解释器将在每次函数调用时执行这个函数。 - 调用
sys.settrace()可以设置一个跟踪函数。此后,解释器将在执行程序的每一行代码时调用这个函数。这可以用于代码覆盖率分析或其他精细控制。
设置这些钩子后,分析器就能检查栈帧(Stack Frame)。
理解栈帧(Stack Frame)
Python解释器通过栈帧来跟踪函数调用链的状态。例如,函数foo调用bar,bar调用baz。每个栈帧都包含许多信息:
f_back:指向调用者的栈帧。f_globals:该帧可访问的全局变量。f_code:一个代码对象,包含关于正在执行的代码的信息。其中,co_name属性给出了当前正在执行的函数名称。

分析器通过检查当前栈帧的f_code.co_name,就能知道正在执行的是哪个函数,并进行记录。
确定性分析的工作流程图示

假设主程序依次执行function1、function2、function3:
- 解释器进入
function1,立即调用你通过sys.setprofile设置的分析函数。分析函数记录函数名和开始时间,然后将控制权交回解释器。 - 解释器执行
function1。 function1执行完毕,系统再次调用你的分析函数。分析函数记录函数名和结束时间,然后返回。- 解释器继续执行
function2和function3,过程同上。
关键在于,对于每个函数调用(或每一行代码,如果使用settrace),你的分析函数都会被调用。
确定性分析的优缺点
优点:提供极其详细和精确的执行信息,是开发和测试阶段优化代码的利器。
缺点:
- 速度慢:为每个调用或每行代码都运行钩子函数会带来显著开销,难以在生产环境中使用。
- 难以捕捉生产环境行为:用户的使用模式可能与开发者不同,确定性分析在生产部署中难以实施。
- 线程处理复杂:默认的确定性分析不是线程感知的。如果一个函数在执行过程中被线程切换中断,分析器记录的时间可能会包含其他线程的执行时间,导致数据失真。
统计性能分析 ⏱️
上一节我们探讨了确定性分析的优缺点,本节中我们来看看它的替代方案:统计性能分析。

统计性能分析器通过周期性采样来了解程序的运行状态,而不是监控每一次调用。其核心思想是:让一个独立的采样线程大部分时间在休眠,定期醒来“窥视”一下主程序正在执行哪个函数,并记录下来。
统计分析的工作流程图示
主线程依然执行function1、function2、function3。与此同时,一个采样线程在运行:
- 采样线程通过
time.sleep()休眠一段时间。 - 采样线程醒来,查看主线程当前的调用栈,记录下正在执行的函数名。
- 采样线程再次休眠,重复此过程。

通过这种方式,统计分析器能以极低的开销获得程序在各函数中时间分布的估计。
统计分析的实现方法
有多种技术可以实现统计分析器:
- 使用POSIX定时器:设置系统定时器中断进程,在中断处理程序中检查调用栈。例如
statprof和plop包。缺点:不支持Windows系统。 - 使用Ptrace系统调用:允许一个进程检查和控制另一个进程。例如
PyFlame分析器。缺点:同样不支持Windows,且较为复杂。 - 使用Python内置功能:利用
sys模块的纯Python函数,实现跨平台。例如pyprofile和本教程将重点介绍的oxprofile。
为什么需要跨平台的纯Python实现?
- 可移植性:使分析器能在Windows、Linux等多种环境下工作。
- 易于理解和调试:纯Python实现对我们Python开发者更友好,便于理解内部机制,确保其在生产环境中稳定可靠。
- 实际需求:许多应用(如游戏、消费软件)运行在Windows上。在这些场景中使用高开销的确定性分析会严重影响用户体验(如降低帧率),使得性能问题难以复现和诊断。
核心武器:sys._current_frames() 🎯

上一节我们了解了统计分析的多种实现路径,本节中我们聚焦于最通用、最Pythonic的方法:使用sys._current_frames()。
sys._current_frames()函数返回一个字典,映射了每个线程ID到其当前正在执行的栈帧。通过它,我们可以在不干扰目标线程的情况下,直接窥视其调用栈。

基于 sys._current_frames() 的采样器原理
- 启动一个独立的采样线程。
- 在该线程中循环:休眠 -> 调用
sys._current_frames()-> 遍历结果,从栈帧中获取函数名(frame.f_code.co_name) -> 记录(如增加计数器)-> 继续休眠。 - 主程序完全不受影响地运行。
一个极简的采样器循环代码如下:
import sys, time
def sampler_thread():
while True:
time.sleep(0.1) # 采样间隔
for thread_id, frame in sys._current_frames().items():
function_name = frame.f_code.co_name
# 在字典中增加该函数名的计数
counts[function_name] = counts.get(function_name, 0) + 1
控制开销的关键在于采样间隔。间隔越长,开销越小,但需要更长的运行时间来获得准确数据;间隔越短,数据越准确,但开销会增大。理论上,当间隔趋近于零时,统计分析就趋近于确定性分析。
统计分析的输出

调用分析器的show()方法,你会得到一个列表,通常包含:
- 函数名
- 所在模块
- 命中次数(采样时看到该函数正在执行的次数)
- 占总命中次数的百分比
这个百分比近似代表了该函数消耗的CPU时间比例。你可以据此判断优化重点。例如,如果send函数占了28%的命中,那么优化它很可能带来显著的性能提升。
从理论到实践:编写健壮的统计分析器 🛡️

上一节我们看到了一个简单的四行代码采样器,但在实际编写多线程程序时,我们需要考虑更多边界情况以保证其健壮性。本节中我们来看看如何加固我们的分析器。
以下是构建健壮采样器需要注意的几个核心问题及其解决方案:

1. 处理线程上下文切换
问题:采样线程在迭代sys._current_frames()返回的帧字典时,解释器可能切换到另一个线程。等切换回来时,之前获取的栈帧可能已经失效(函数已执行完),导致访问过期内存而引发错误。
解决方案:使用sys.setswitchinterval()临时增大解释器的线程切换间隔,在采样期间尽可能阻止切换。
import sys
old_interval = sys.getswitchinterval()
sys.setswitchinterval(10) # 临时设置为10秒内不切换
try:
# 快速迭代并记录帧信息
for thread_id, frame in sys._current_frames().items():
# ... 记录逻辑 ...
finally:
sys.setswitchinterval(old_interval) # 确保恢复原间隔
使用try...finally块确保无论是否发生异常,线程切换间隔都能被恢复。
2. 处理并发数据访问

问题:采样线程在修改记录命中次数的字典时,主线程或其他线程可能同时调用show()方法来读取这个字典。在遍历时修改字典会导致Python解释器崩溃或产生不可预知的行为。
解决方案:使用线程锁(threading.Lock)来同步对共享数据(字典)的访问。
import threading
class Profiler:
def __init__(self):
self._counts = {}
self._lock = threading.Lock()
def _record_sample(self, function_name):
with self._lock: # 获取锁
self._counts[function_name] = self._counts.get(function_name, 0) + 1
def show(self):
with self._lock: # 获取锁
# 安全地读取和返回 self._counts 的数据
return sorted(self._counts.items(), ...)
3. 应对底层崩溃
问题:在多线程编程中,如果发生内存损坏等严重错误,可能无法产生正常的异常堆栈跟踪,给调试带来极大困难。
解决方案:启用faulthandler模块。它能在程序崩溃(即使内存损坏)时,尽量打印出一些基本的调试信息。
import faulthandler
faulthandler.enable() # 在程序启动时调用
4. 其他实用技巧
- 将采样线程设为守护线程:设置
thread.daemon = True,这样当主程序退出时,采样线程会自动终止,避免程序无法正常退出。 - 注意
time.sleep的最小粒度:在大多数系统上,time.sleep的精度在1-10毫秒左右,这限制了最短的有效采样间隔。
实战:使用OX Profile与Web集成 🌐
理论已经完备,本节我们来看一个具体的统计性能分析器实现——oxprofile,并了解如何将其集成到Web应用(如Flask)中。
使用OX Profile
安装和使用非常简单:
pip install oxprofile- 在代码中导入并启动分析器。
from oxprofile import Profiler
profiler = Profiler().start() # 启动分析器
# ... 运行你的程序 ...
profiler.show() # 显示分析结果
profiler.cancel() # 停止分析器
集成到Flask应用
oxprofile提供了Flask蓝图,可以轻松集成:
from flask import Flask
from oxprofile.contrib.flask import oxprofile_bp
app = Flask(__name__)
app.config[‘OXPROFILE_USERS‘] = {‘admin‘: ‘password‘} # 配置访问凭证
app.register_blueprint(oxprofile_bp) # 注册蓝图
集成后,你的Flask应用会新增几个端点:
/oxprofile/unpause: 开启分析器。/oxprofile/pause: 暂停分析器。/oxprofile/status: 查看当前分析状态和结果。/oxprofile/set_interval: 调整采样间隔。
这种集成方式使得在生产环境中对Web服务进行实时性能采样变得非常方便,而且开销可控。
sys._current_frames() 的创意应用 💡
sys._current_frames()的功能远不止于性能分析。本节我们来探索一些其他有趣的用途,展示其强大能力。
- 数据快照与调试:定期快照活动帧的局部变量(
frame.f_locals),帮助理解复杂第三方库的行为或程序状态,而无需修改其源码。 - 混沌测试:在测试环境中,随机修改
frame.f_locals中的值(例如,给所有整数加一),以测试程序的健壮性和异常处理能力。 - 统计调试器:在程序崩溃时,自动保存最近若干次采样的完整堆栈跟踪和局部变量快照,为事后分析提供宝贵线索。
- 统计代码覆盖率:以较低的开销,周期性开启采样并记录执行到的代码行,来了解生产环境实际运行的代码路径。

总结 📝

本节课中我们一起学习了Python性能分析的方方面面:
- 性能分析的目的是发现程序瓶颈,指导优化方向。
- 确定性分析(如
cProfile)精度高但开销大,适用于开发测试。 - 统计性能分析通过周期性采样来估算时间分布,开销极低,适用于生产环境。
- 利用
sys模块的setprofile、settrace和_current_frames等函数,我们可以深入解释器内部,实现各种分析工具。 - 实现一个健壮的统计分析器需要注意线程安全(使用锁)、上下文切换(调整切换间隔)和异常处理(使用
try...finally和faulthandler)。 oxprofile是一个基于sys._current_frames()的、跨平台的统计分析器实例,并可方便地集成到Web框架中。sys._current_frames()是一个强大的调试和自省工具,其应用范围远超性能分析本身。

希望本教程能帮助你理解Python性能分析的核心,并激发你探索sys模块更多潜能的兴趣。
009:测量与误测




在本节课中,我们将学习算法公平性的基本概念、常见的数学衡量标准及其潜在缺陷,并探讨如何通过更负责任的方法来构建更公平的机器学习系统。

大家好,欢迎。今天的演讲者是 Manojit Nandi,他将与我们谈论算法公平性的测量与误测。




我该如何做事情?好了,这就是事情发生的方式。抱歉,稍等一下。


大家好,感谢大家今天下午来参加我的演讲,主题是算法公平性的测量与误测。我是 Manojit Nandi。


我是一名数据科学家。我根据 Google Cloud 计算机视觉 API 快速介绍一下自己。我将自己的照片通过 Google Vision API 处理,生成了一些标签。让我们看看其中的一些标签,以及它们的准确性。我是舞者吗?是的,我是一名马戏团空中舞者。我每周工作 40 小时作为数据科学家,因为我得以某种方式支付房租。我是娱乐型的吗?希望如此,你将和我一起度过接下来的 40 分钟。我有趣吗?嗯,大多数时候。最后,我是女孩吗?这个不对,我是一个男性表现的顺性别男性。那么,为什么 Google Vision 会认为我是女孩呢?实际上,不仅仅是 Google Vision,如果我通过微软的视觉 API 来处理,结果也会相似,它会说这是一张女孩的照片。
所以,问题是,当他们说这是一个女孩时,他们到底在寻找什么?他们是在说我与他们理解的女孩有多相似吗?是因为这是一个舞者,而我训练集中的所有舞者都是女孩,所以这也是一个女孩,还是因为某些身体特征让我被系统认为可能是一个女孩。虽然这似乎是一个有趣无害的例子,但这种自动性别识别的系统在很多地方都被使用,例如为了向你展示更好的广告。


数据的真正目的不是为了向你展示更好的广告。想想看,这听起来很傻,但实际上相当危险。如果你对什么是男性或女性有一些原型性的理解,你实际上可能会错误性别化一些人,这确实是一个很大的问题。华盛顿大学的研究者已经做了一项调查,研究了人机系统中对性别和酷儿身份的理解。他们的总体发现是,人机系统中对性别的理解固有地排斥跨性别者。
所以现在,随着我们努力成为以数据驱动的领导者,并使用这些类型的系统,我们所使用的系统本质上是排除跨性别者的。我们做出的决策本质上忽视了跨性别社区的存在和生活经验。我希望这个例子能激励我们关注公平和伦理,尤其是在进行数据科学和机器学习时。

那么,什么是算法公平性?

算法公平性是一个旨在缓解不当偏见对人们在机器学习中歧视影响的研究领域。这是一个有点奇怪的事情,因为偏见在数据科学和机器学习中是一个非常广泛的术语。目前,这类研究的重点是提出公平性的数学定义。我们有这个公平性的数学定义,我们将找到一些解决方案。但我们希望这能很好地映射回原始问题。
在过去的一年左右,我们看到了一些反对意见。例如在一篇名为“公平抽象与社会技术系统”的论文中,存在一种形式主义陷阱,我们对此过于关注数学形式化的问题。即使你解决了数学形式化,它在现实世界中的解决方案也并不容易转化。关键在于,像公平这样本质上是一个社会和伦理的概念,无法通过数学定义或统计指标完美捕捉或表现。



我想稍微谈一下这个问题。我们在使用算法和数学,而数学本身不能是种族歧视,这太荒谬了。实际上,这个问题是在一月份提出的,当时一位极右翼记者批评了纽约国会女议员亚历山德里娅·奥卡西奥-科尔特斯,她表示研究表明,算法在不同情况下对黑人和西班牙裔人士存在歧视。
因此,当我们说一个算法是种族歧视或性别歧视时,我们并不是说数学本身是种族歧视或性别歧视。我们并不是说质数本身就是种族歧视的,逻辑回归本身并不是性别歧视的。我们谈论的是我们使用数学、使用算法的方式如何加强社会不平等。质数本身不是种族歧视的,但我们可以使用质数破解密码系统的方式在现实世界中具有实际影响。回归分析来决定谁能获得信用贷款或银行贷款,这在现实世界中有实际影响。这是我们在设计这些算法时需要考虑的,它们并不是孤立存在的。

算法公平性研究领域是嵌入在人类系统中的。现在让我们揭示所谓的公平性、问责制和透明度。这个跨学科的研究领域探讨如何使机器学习和技术系统关注公平的理念。
在过去几年中,公平、正义和平等的话题得到了迅速发展。你可以看到专门的会议,比如 ACM FAccT。我们看到许多关于这方面的开源库。今天我们在会议上还有关于模型公平性的另一个讲座。如果有兴趣参加的人,我已查看过那场讲座,我相信我们的两场讲座之间有足够的差异,让你参加两场都会有所收获。

这不仅仅是研究人员在讨论,我们在流行媒体中也谈论这一点。凯西·奥尼尔的《数学杀伤性武器》,维吉尼亚·尤班克斯的《自动不平等》,以及萨菲娅·乌莫贾·诺布尔的《压迫的算法》,这三本书都很有趣。这种方法是,算法如何影响人们。我们作为数据科学家,作为机器学习研究者,与我们工作的下游影响似乎是分开的。
计算机科学家训练时主要是数学和统计,我们并未被训练去思考伦理或公共政策。但我们需要理解我们的工作有下游影响。我们不想扮演伦理学家的角色或公共政策的角色,但我认为深层次上,我们都不想引发灾难,我们都不想对他人造成伤害。没有人想成为这些书中所写的人。
研究中的观点是,好的事情我们可以做,而流行媒体则讨论这些可能如何伤害人们。关于道德的一面,我不想伤害人,但我可以鼓励你考虑这些问题,通过法律法规。也许你不在乎伤害他人,但你在乎保住工作,因为如果你违反了某些规定,就会触犯法律。然后你可能会失去工作,因为你的公司将被罚款数百万美元。
因此在美国,例如,我们有差异影响法,这些法则基本上规范了如何发放贷款,如何雇佣人,以免伤害他们。此外,在欧盟,我们现在有 GDPR,一套总体的法律和规则,关于我们如何思考使用算法处理个人数据的方式。我们必须考虑,你能解释一下为什么你的模型会这样吗?你能解释一下为什么你的神经网络没有给某人贷款吗?他们能否选择退出。
接下来,我们来谈谈不同类型的算法偏见。

这基于微软和康奈尔大学研究人员的一些工作。这在现在的会议上被展示出来了。有人告诉我,它的发音像“偏见”,我不确定这是不是玩笑,但这些研究确实是种分类。



我们确定了思考算法偏见的三大类方式:分配偏见、表征偏见和武器化。首先,让我们谈谈分配偏见。很多关于偏见的问题集中在贷款或就业分配的问题上。这些实际上就是二元分类问题。例如,男性是否比女性更容易获得软件工程职位,白人是否比非白人更容易获得信用贷款。这实际上是一个二元的“是”或“否”。而这正是大多数研究所集中于的,因为这相对容易形式化为数学问题且容易解决。
因此去年新闻报道中提到,亚马逊不得不停止使用其 AI 招聘工具,因为他们发现它只是对女性有偏见。它关注像“贾里德”这样的名字,或者你曾在长曲棍球队的经历,这些特征与软件工程师的能力无关。因此这是一种偏见,因为他们意识到模型是对女性有偏见的。
接下来是表征偏见。表征偏见关注的是如何通过机器学习系统传播有害的刻板印象或标签。这通常与语言问题或计算机视觉问题有关。这些都是神经网络的问题,它只是挑选出一个解决方案并进行一些黑箱神经网络的魔法,但我们并不关心它所做的内部机制。这些错误与之前的偏见和分配问题相比是比较难以量化的,这也是为什么现在对此的研究较少。
这有一个著名的例子,就是当谷歌照片在 2015 年发布时,竟然将一群在毕业派对上的黑人孩子标记为大猩猩。对于那些不熟悉的人来说,大猩猩或猿类在历史上曾被用作对美国非裔人士的贬义词。因此这种标签是错误的,但它也引发了对非裔美国人的有害刻板印象。这一点更难量化,因为谷歌照片永远不会将一群白人孩子标记为大猩猩。这是一个专门给这些孩子的标签,助长了这一有害的刻板印象。
其他一些难以量化的例子就是社交媒体滤镜。例如,当你应用社交媒体的花冠滤镜时,个体的肤色变亮。为什么呢?因为一些社交媒体滤镜会学习到更亮的肤色与美丽相关。因此当我应用这个滤镜时,会让他们的肤色变亮。所有这些都像是一个很难量化的问题,它正在优化应该做的事情,但这个解决方案总让人感到不安。
另一个例子是谷歌翻译。当你翻译一些来自马来语的句子时,马来语没有性别代词。你可以说“他们是医生”、“他们是士兵”。但你翻译成英语时,它就会自动赋予性别:“他是一名医生”、“他是一名士兵”、“她是一名护士”。同时,谷歌翻译在进行这种数学观察时,这很难说为什么这是错误的。这对这些句子的翻译是有效的,但总有一些东西让你感到不安。不过值得一提的是,谷歌实际上已经做出了一种临时修复。当你从一种无性别语言翻译成性别语言时,它实际上会显示这在性别上是有区别的。因此从“他们是医生”翻译成“她是医生”或“他是医生”。这对这个问题的快速修正来说仍然不可用。在谷歌翻译的 iOS 和 Android 版本中,你仍然会看到一些偏见。当你进行谷歌自动补全时,如果你搜索“他是个……”,你可能会得到“医生”或“士兵”的结果。如果你搜索“她是个……”,结果可能不同。
最后我们需要达到的目标是武器化。最后这个版本被称为机器学习的武器化。关键思想是,作为数据科学家,我们被教导去训练模型,产生一些指标,但我们并不真正考虑到我们可能如何伤害人们或如何被误用。2017 年,斯坦福大学的一组研究人员试图创建一个基于人脸图像预测人们性取向或性偏好的算法。有一个尝试预测某人是异性恋还是同性恋的工具。值得注意的是,世界上有些国家将同性恋视为犯罪。如果你作为同性恋者公开出柜,你可能会被国家处决。还有许多地方虽然没有被犯罪化,但作为一个公开的同性恋者是非常危险的。因此如果某人使用这个模型,他们可能会把它作为武器对抗那些国家的同性恋者。要考虑的是,没有任何数学衡量标准或单元测试可以告诉你这主意不好。这将要求你在场中有文化人类学家和历史学家来告诉你,“嘿,这可能会伤害人们。”这就是为什么我真的很高兴有这样的讨论开始,数据科学家和机器学习技术专家开始思考伦理培训。我们需要进行像加州大学伯克利分校或卡内基梅隆大学这样的学位项目,它们开始纳入伦理成分。我认为这确实是培养一种更加关注我们工作对人们下游影响的技术文化的开始。
现在,让我们谈谈不同类型的公平性衡量标准。

这些就像是数学定义。我将真正讨论它们是什么以及它们的一些主要缺陷。这基于斯坦福大学两位研究人员 Sam Corbett-Davies 和 Sharad Goel 的工作。他们基于这些行为衡量标准和不当衡量标准。这次演讲的标题就是基于此。因此我们将讨论差异。

算法公平性的定义有很多。去年 Arvind Narayanan 教授的演讲真正展示了在他演讲时有 21 个算法公平性的定义,现在已经有超过 30 个。作为计算机科学家和统计学家,我们会想,好的,有三个定义,肯定有一些比其他的更好。必须有一个真正的公平定义可以使用。但这其实并不真实。为什么?因为公平是社会和文化的产物。50 年后我们认为公平的事情,与今天认为的公平是不同的。但这些不同的定义可以归纳为三大类:反分类、分类公平和校准。
我要给个快速警告,这里谈话的故事变得有些技术性和数学性。
首先,这些反分类措施是什么?也就是说,你有这些保护特征,比如种族、性别、宗教和出生地等特征。你有未保护特征用于模型。而这些反分类特征理想情况下是,当你做出决策时,它应该有效地忽略那些人口特征。忽略种族、忽略性别、忽略宗教。决策或行为的方式,就是这个个体公平的概念:你根据人的品格判断一个人,而不是肤色。两个人如果拥有相同的未保护特征,应该受到平等对待。
但这里的问题在于,像是 50 年前被认为公平的概念现在就不再被认为公平。你不能仅仅从模型或数据中抛弃种族和性别,感觉好像我完成了。因为我们现在开始意识到有一些代理特征,我们需要担心,比如那些并不直接编码种族或性别但与之高度相关的特征。
举个例子,2012 年有个故事,办公用品公司 Staples 推出促销:如果你住在竞争商店 20 英里内,我们将给你特殊优惠券折扣,促使你光顾我们的商店。问题是,那些住在离竞争对手 20 英里的社区,通常是富裕的郊区社区。住在富裕郊区的人群有特定的种族人口特征。所以,当你的模型基于位置进行歧视或做出决定时,它就是无意中表现得好像是在基于种族做出决定。因此 Staples 因为不同影响而遭到巨额罚款。
因此这些特征对于设计关注公平的模型非常有用。那么什么是关注公平的模型呢?这有点像传统的机器学习监督模型,现在有了这个需要考虑的附加组件,即那些受保护的特征。在标准机器学习中,你有特征 X 和标签 Y,你基本上是想将特征 X 映射到标签 Y。而现在使用公平性算法时,你有 X、Y 和受保护属性 Z(例如种族、性别、宗教、出生地)。你想要的就是学习能够学习标签的特征,但不想意外地学习种族或性别。
一个很酷的算法是公平性意识的 GAN。我认为这是一个非常聪明的想法。它与 GAN 的工作原理是将这两个子模型链接在一起:一个是你的标准机器学习分类器,它试图学习特征并预测标签;另一个则在尝试处理标签并学习受保护类别。因此你可以考虑一种双重的思维方式:我试图学习一个好的分类器,而你试图通过说“那个分类器偶然学习了种族或性别”来打破我。这就是它真正运作的方式。在这个红框中的损失函数是说“我是一个好的分类器”,学习哪些特征来预测谁能获得贷款。而蓝框中的则是在说,“好的,你学习了谁可以获得信用贷款,你是否意外地学习了种族或性别?”如果你研究机器学习和正则化,比如最初的回归或逻辑回归,它看起来有点像是一个正则化:我想成为一个好的分类器,但我不想在这种情况下过于复杂。这种复杂性有点像“我不想意外地具有歧视性”。这有时被称为准确性与公平性之间的权衡。老实说,我不喜欢这样称呼它为权衡。因为如果你真的思考我们为什么要做这一切,原因在于我们的训练集是有偏的,因为我们的测试集是有偏的,这意味着我们的测试标签是有偏的。就像我们的答案键是错误的。我们不想要完美学习测试集标签的模型,因为它也在学习这些偏见。所以我们预计准确性会下降。我也不喜欢称之为权衡,因为这会与一种有害的想法相关联,即促进多样性需要你参与亚最优选择。因此你看到,这就像科技招聘一样。假如一家软件公司说“嘿,我们的 50%的软件工程师是女性”,这是好事。但总会有人在推特上说:“嘿,我有个绝妙的主意,为什么不根据人才而不是性别来招聘最优秀的人呢?”这是与一种有害观念有关的,认为为了实现多样性,你必须选择较不理想的候选人或技能较低的候选人。这完全不正确。多样性对科技领域是非常有益的。拥有不同生活经历和不同思维方式的人在房间里进行思考是重要的,因为他们可以在你犯错的时候指出你。这对于数据科学非常重要。
那么,使用这些反分类措施有什么危险呢?听起来好像是个好主意,我们应该根据人们的重要特征来评判他们,而不是不小心学习了那些保护特征。通过移除这些保护特征,我们在某种程度上忽视了作用于不同人口群体的潜在过程。这些指标似乎是在努力使结果平等,但实际上,公平是使过程平等。
这是一种危险,类似于监狱再犯率。当你是一名囚犯并且在辩护时,你是否可以获得保释?监狱如何运作就是这样:它给你一个分数,范围在一到十之间,这大致表明放你出去的风险有多大——如果我们释放你,你再犯罪的可能性是什么?问题在于,男性被告在同一分数下比女性被告更容易再犯。因此,如果你在这种情况下忽略性别,取两条线的平均值,你就会得到平均线高于女性被告的比率。因此如果你把阈值设定在略低于 60%时,而对我来说,如果你仅仅看性别,你会说“好吧,我们不会释放女性被告,当她们的分数是八分或以上”。而如果你取平均值,你现在有了那个阈值,如果是七分或以上,我们就不会释放任何人。因此你现在拘留了更多女性,这与之前的情况不同。如果你单独将性别视为一个特征。
接下来是分类公平。分类公平可以视为一种扩展。在传统机器学习中,你根据准确率、精确度、召回率或 AUC 等指标来评估你的模型。而现在你需要在不同的人口群体中进行这些指标的评估。
例如,这是著名的性别研究案例。麻省理工学院的学生 Joy Buolamwini 展示了这些商业人脸识别算法(如微软、IBM 以及 Face++)在识别白人男性面孔方面非常优秀,对白人女性面孔也很擅长,对黑人男性面孔的识别也不错,但在种族与性别的交集上,对于黑人女性面孔的识别效果极差。因此在这些不同的人口群体中,黑人女性的准确性显著低下。Joy 还在 YouTube 上有一段非常酷的视频,作为一首口语诗,讲述了商业计算机视觉系统经常错误地识别著名的黑人女性,如米歇尔·奥巴马或奥普拉。
而这些类型的指标或这些成本平衡指标通常用于执行法律法规。例如平等就业机会法在幕后使用这些分类指标。
那么最常见的指标是什么呢?这些指标中最常见的是人口平衡,即不同人口群体或受保护群体获得积极结果的频率。审计模型用于评估不同影响,像是贷款的发放。查看不同人口群体,然后说,白人男性获得贷款的比例与非白人非男性获得贷款的比例之间的差异最多应为 20%。这就是 80% 规则的含义:他们的比例必须在彼此的 80% 范围内。而这听起来不错。如果我们是一家银行,你需要平衡向不同人群发放的贷款数量。但问题是你可以满足这一直接要求,但可能没有真正考虑到你行为的长期后果。
这是加州大学伯克利分校研究人员的一篇论文,讨论了公平机器学习的深远影响。他们展示了如果你进行这种人口平衡,在橙色人群和蓝色人群之间,你向蓝色人群提供了更多贷款,这些人随后可能违约,因此他们的信用评分下降,整体信用评分分布也随之下降。所以如果你考虑满足“我们必须使积极结果平等”的直接约束,然后你可能会在长期内伤害他们。
另一种考虑方式是,在招聘时,比如我们需要确保招聘女性的比例与我们在技术职位上雇佣男性的百分比相等。如果那些女性因为有毒的工作环境在五六个月内离开,你真的值得这个金星吗?我不这么认为。
接下来是错误的积极率的平等。这正如名字所示。现在我们不是在关注积极结果,而是在关注错误的积极率。错误的积极率就像模型说“是”,但实际上“不是”。这有时被称为平等机会。错误的积极率其实就是错误的积极数量除以(错误的积极数量加上真正的负面数量)。如果你想降低错误的积极率,理想情况下你应该专注于减少错误的积极数量。但如果你想想,我可以通过增加系统中的真正负面数量来降低错误的积极率。
想象你是一个警察局长,有人来找你说,哦,你的拘留过于严格,逮捕的黑人男性人数太多,拒绝保释或假释的人数过高。那么你能做些什么来降低这个错误的积极率呢?理想情况下,你是减少错误的积极率,给予更多本应不被保释的黑人男性保释。但你也可以增加真正负面的数量。真正的负面是什么?真正的负面是一个被逮捕的人,他们被释放,并且没有再次犯罪。那些人是轻罪的犯人。所以我可以增加系统中的真正负面数量:你逮捕更多轻罪的人并释放他们。哇,错误的积极率就下降了。你被教导过,你达成了平等,这不是很好吗?你只需逮捕更多的黑人男性,针对轻罪。哦,这太糟糕了。如果你不考虑这一点,只是优化那个指标,而不考虑导致这些数字的社会因素,你最终会伤害我们想要帮助的脆弱群体。
最后一个是校准。校准是一个非常难以用通俗语言解释的事情。它涉及统计校准。真正的思考方式是,任何事件的发生,比如谁赢得了一场政治选举,有一个真实的结果。我们知道谁赢了,有人没有赢。我们正在弄清楚我们的模型在多大程度上实际预测了这个结果。
例如使用再犯率,给人们的评分从 1 到 10。或者儿童保护服务,孩子被给予一个评分,从一到二十表示孩子在危险中的程度。比如一分表示这个孩子在家庭中并没有真正的危险,而二十分则意味着这个孩子处于严重危险中,必须立即将他们带离家庭。
每当你谈论算法公平性时,必须提到这个 Compass 例子。所以这是不可避免的 Compass 分数,从一到二十。如果得分足够高,那么在某个阈值 t,例如 15 时,他们会把孩子从家庭中带走,因为孩子在那里的危险。如果你有一个白人孩子和一个黑人孩子,他们的得分都是 15,那么这应该是最重要的:他们应该被认为处于同样的危险水平,他们在那户家庭中受到伤害的机会应该都是 70% 或 75%。考虑统计校准,如果两个人得到了相同的分数,那么种族人口统计学应该没关系。应该只在乎我们重视这个分数,而这个分数就是最终的依据,我们仅基于这个分数做决定。如果模型校准良好,那么 15 对于两者来说都是界限。
这如何运作呢?CPS 会进入家庭进行评估,并给这个孩子一个从白人家庭到黑人家庭被移除的儿童百分比。我想关键在于得分对于相同的人意味着相同的事情。
我想在我之前的幻灯片中讨论这些指标时,通常底部有一个小要点说危险,这就是为什么这是不好的。但我在这里没有,因为我有一个完整的幻灯片专门讨论校准的问题。

例如,我认为这是一个重要的案例研究,因为关于 Compass 的问题是什么?这就是监狱和再犯算法。2016 年出版的 ProPublica 表示,使用 Compass 来决定谁可以保释时,实际上是在保留黑人被拘留的比例更高,拘留那些如果被释放就不会再次犯罪的黑人被告。因此 ProPublica 的 Northpoint(该 Compass 算法的开发小组)争辩说,我们的风险分数是良好校准的。如果你得分 8 或者是我们拘留你,因此分数模型是良好校准的。但问题在于得分的基础分布并不是。如果我们给黑人男性的分数是 8 或以上,而我们给白人男性的分数则很少,那么很明显,评分为八分或以上的黑人男性被拘留的机会很高,因为他们更可能获得更高的分数。因此你的模型并不是种族歧视的。而这一点也展示了使用这些模型的另一个重要方面:在这些指标中,你可能在某些指标上表现很好,例如统计校准,但在另一些指标上(如假阳性率)表现很糟。因此,仅仅盲目优化这些指标将导致实际上并不能产生良好结果的解决方案。
那么我们该怎么办?我打算提出一种低成本的解决方案。


一种定量的低技术解决方案就是撰写更好的文档。这听起来可能很傻,但我认为我们现在在人工智能伦理方面所面临的一部分问题是硅谷有些封闭。硅谷认为这里充满了技术天才,他们相信自己达到了某种境界,不需要平民来告诉他们如何做他们的工作,他们会自己处理,不需要政府法规,也不需要外部人士来帮助他们。这完全不是真的。
你可以想象,像汽车测试这样的例子,历史上更好的文档能够更好地传达数据集的构成以及这些模型的工作原理。因此即使你不是文化人类学家或伦理学家,如果你能更好地沟通你工作的结果,也能让人更清楚地理解你的训练和测试过程。

这些人可以评估,比如这会在长期内伤害到某些人群,或者无意中伤害了那些你没有考虑到的群体。因此,关于数据集的这一很酷的想法来自于 Timnit Gebru,她基本上提出的建议是,像汽车行业或临床测试等其他行业通常会有标准化的评估模型的方法,我们应该为数据科学数据集采用类似的标准。
这些汽车测试是针对像专业假人碰撞测试而进行的。专业假人通常具备成年男性的特征,因此在现实世界中,当实际的汽车碰撞发生时,女性和儿童受到的伤害极其严重。现在我们有法律法规规定,必须确保也对专业的成年女性假人和儿童假人进行测试。这份文档我认为能够回答许多问题,比如数据是如何收集的,是否以不排斥个体的方式收集。
我认为这非常重要,因为我确实认为我们关于机器学习的思维方式比较集中在模型上,而不是那么关注于训练集。我们没有考虑到是什么使得一个好的训练集。一个好的训练集不仅仅是缺失数据没有异常值。这确实回答了我是否可以进行统计分析的问题,但并没有回答“我是否排除了我应该关注的个体群体,是否排除了跨性别社区,是否排除了非异性恋个体”。因为我收集数据的方式。我确实认为这将讨论的重点从模型转向了我们如何实际收集好的数据。这之前我们真的没有考虑过。因为我们只是有点假设“好吧,这个数据集存在于虚空中,我是从 Kaggle 上获得的,或者从其他网站上获得的”,但我们并没有真正考虑那个数据集是否真正适合我的问题,还是仅仅是方便可用的。
接下来,模型也是类似的。这项工作是由谷歌研究的 Meg Mitchell 团队和谷歌 AI 公平性小组完成的。因此,他们提出的方案有点类似于标准化机器学习模型的文档。你记录好这个模型是如何产生的,以及在部署之前如何训练它。让我们讨论它应该如何使用,预期的使用场景是什么。它是如何被评估的,而不仅仅是“好吧,是看准确性校准或者是准确度”,还包括它如何在不同的人口群体中进行评估,它如何在这些人口群体的交集上进行评估。我们只是把交叉分析引入到我们进行数据科学和机器学习的方式中。我认为关键点还在于,这些伦理问题是什么。如果你确实设计了一个性取向分类器,伦理问题是什么?这可能会如何被用于伤害他人,或者在同性恋是非法的国家被作为武器。关键在于,更多透明的模型报告将使我们能够更好地沟通应该如何使用我们的模型。因为现在我们有点像是“好吧,我想做计算机视觉,我们就抓取 YOLO 网或 AlexNet,然后把它应用到所有问题上”,尽管那些模型并不是专门为你的特定问题设计的。
最后还有这个工具 Deon。Deon 是一个数据科学项目的伦理检查表。Deon 由一个叫 Driven Data 的组织制作,他们为非营利性政府组织提供咨询,同时也举办数据科学为善的竞赛。所以想象一下 Kaggle 竞赛,但带有以明确强调帮助某种社会公益的方式,例如帮助教师、帮助教育、帮助药物治疗等。这项工具可以在你的代码库中创建一个 Markdown 文件,包含一个检查清单,用于检查不同的事项,比如数据是如何收集的,是否以公正的方式收集,是否覆盖了所有人群,是否排除了某个群体,以及人们如何被告知他们的数据是如何被使用的。
在社会科学中,知情同意是非常重要的。你无法获得研究批准,除非你获得了知情同意。另一方面,在数据科学中,我们能够在不告知用户的情况下对他们进行 A/B 测试。因此你可以对人们进行一些相当糟糕的 A/B 测试。为什么你要这样做而不告知他们?同时也要考虑数据存储。我认为这很重要,因为我们现在有了被遗忘的权利的概念。如果有人不想让他们的数据集被用于算法中,他们应该能够说“我不想让我的数据被使用,从你的记录中删除它”,并且你应该能够轻松做到这一点,应该能够遵守这一点。这也是 GDPR 的一部分。当你部署模型时,你如何看待如果模型出现问题该如何下架?有个新闻说你的模型正在伤害这些群体的人,你如何将模型从生产环境中移除?我认为提前考虑这些问题很重要。
我想说这些模型、数据表和检查清单并不是万无一失的。这并不能完全防止你伤害那些你不想伤害的群体。但我认为这算是一个好的第一步,有助于让我们在进行数据科学时稍微聪明一些。
最后我想特别提到 AI Now 研究所。这是纽约大学的一个研究机构,关注 AI 的文化和社会影响。因此他们每年举办关于伦理、组织和问责的研讨会。他们最近也发表了一篇关于 AI 多样性危机的论文。所以与 AI 相关的许多问题,如我们缺乏多样性、缺乏不同的声音或意见在场,这导致很多临时解决方案。如果你讨论的是算法的偏见,而不是仅仅依赖于一个自动化算法,而是让一个人来参与检查决策,那么如果你参与检查的大多数人都是白人男性,他们并不了解非白人群体的生活经历,他们将无法检查你的算法所做的决策。他们没有意识到这是其他不同人群的问题。因此,我认为这很重要,因为它也涉及到一些没有多样性的长期技术后果。在人工智能、机器学习和数据科学中缺乏多样性,这真的很糟糕。

这里是我在演讲中提到的所有论文,如果你想要的话,可以在之后查看。是的,我希望这次演讲能引人入胜,希望你们都能学到一些东西。如果有时间,我希望你们去看看关于衡量模型公平性的另一场演讲。

在本节课中,我们一起学习了算法公平性的核心概念。我们探讨了算法偏见的三种主要类型:分配偏见、表征偏见和武器化。我们深入分析了三种常见的公平性数学衡量标准——反分类、分类公平和校准——并指出了它们各自的局限性和潜在风险。最后,我们讨论了通过撰写更好的文档(如数据表、模型卡和伦理检查表)来促进算法公平性的实践方法。记住,公平不仅是一个数学问题,更是一个深刻的社会和伦理问题,需要跨学科的思考和持续的努力。
010:提升Python项目安全性的实用指南


在本节课中,我们将学习如何为Python项目选择和使用有效的安全工具。我们将从检测已知漏洞开始,然后探讨如何通过代码分析提升代码质量,最后总结一套实用的安全实践流程。


概述:为何需要Python安全工具?
许多开发者认为Python“本质上是安全的”,或者仅靠代码风格检查工具(如Pylint)就足够了。然而,这种想法可能导致项目暴露在过时依赖、代码缺陷等安全风险之下。本教程将介绍一系列专门用于提升Python项目安全性的工具和实践。
检测已知漏洞
上一节我们指出了对Python安全性的常见误解,本节中我们来看看如何系统地检测项目中已知的安全漏洞。

使用通用漏洞数据库(如CVE)手动检查依赖项非常困难,原因包括:包名映射不准确、数据不完整、以及许多漏洞未被正式记录。
值得庆幸的是,已有成熟的工具可以自动化完成这项工作。
以下是两款推荐的漏洞检测工具:
- Safety:检查项目依赖是否存在已知漏洞。它维护着一个定期更新的漏洞数据库,并支持命令行和Web界面。
- Pipenv Check:Pipenv套件中的工具,同样能检查依赖漏洞,并提供CVE的简要描述和建议。
核心建议:选择其中一款工具并将其集成到你的工作流程中。它们能有效发现因依赖未更新而引入的漏洞。

使用Bandit进行代码安全分析

解决了已知漏洞问题后,我们需要关注代码本身的质量。本节中我们来看看如何使用静态分析工具发现潜在的安全缺陷。
Pylint等工具主要关注代码风格和一致性,附带的安全收益有限。Bandit则是一个专门为发现Python代码安全问题而设计的工具。
安装与运行Bandit非常简单:
pip install bandit
bandit -r path/to/your/code/
Bandit会扫描代码,识别如使用assert语句进行安全验证、不安全的反序列化(pickle)、弱加密等常见问题。

以下是使用Bandit时需要注意的关键点:

- 避免扫描虚拟环境:不要对包含虚拟环境(
venv/)的目录运行Bandit,否则会产生大量无关警报。 - 理解而非盲从:Bandit会报告“危险模式”,但并非所有报告都是必须修复的“错误”。你需要根据上下文判断。
- 利用文档:Bandit为每个问题提供了详细说明和修复建议的链接,是很好的学习资源。
核心概念:将Bandit作为代码审查的辅助工具,帮助团队聚焦于潜在的安全风险点。
构建安全开发流程
拥有了检测漏洞和分析代码的工具后,我们需要将其融入开发流程。本节我们将总结如何系统地应用这些工具。
安全不仅仅是工具,更是一种实践。除了上述工具,还可以考虑更高级的静态分析工具(如对开源项目免费的Coverity Scan),但以下是最低限度的核心实践:
- 集成到持续集成(CI):在CI流水线中自动运行漏洞检查和代码安全扫描。
- 在发布前进行检查:确保每次发布前都执行了安全检查。
- 开展安全导向的代码审查:学习并实践针对Python的安全代码审查,警惕如硬编码密钥、输入验证不足等通用问题。
大多数安全问题是跨语言通用的。OWASP Top 10、SANS Top 25等资源中的原则同样适用于Python,关键在于寻找并理解Python语境下的具体案例。
总结
本节课中我们一起学习了提升Python项目安全性的系统方法:
- 使用Safety或Pipenv Check来自动化检测依赖项中的已知漏洞。
- 使用Bandit进行静态代码分析,发现代码中的潜在安全缺陷,并理解其报告。
- 将安全工具集成到CI/CD流程中,并在发布前强制执行检查。
- 培养安全代码审查习惯,认识到许多安全问题是跨语言存在的。

记住,没有“绝对安全”的语言,主动采用这些工具和实践能显著提升你的Python项目的安全基线。
011:为安全环境构建可重现的Python应用程序 🛡️


在本节课中,我们将学习如何为高安全需求的环境构建可重现的Python应用程序。我们将探讨从源代码管理到最终部署的整个流程,并重点介绍如何通过现有工具和最佳实践来确保应用的安全性、真实性和可审计性。
概述与背景


上一节我们介绍了课程的主题和目标。本节中,我们来看看演讲者Kushal Das的背景以及他所面临的实际安全挑战。


Kushal Das是一位公共利益技术人员,服务于新闻自由基金会。该组织致力于保护、捍卫和赋能公共利益新闻。他的工作包括与媒体组织合作,进行数字安全培训、编写软件以及提升系统安全性。
此外,Kushal还是Python软件基金会的董事、CPython核心开发者以及Tor项目的核心成员。这些经历让他深刻理解在高风险环境中构建安全软件的重要性。

面临的挑战:安全性与可用性
上一节我们了解了项目背景,本节中我们来看看在安全环境中开发应用时遇到的核心矛盾。

Kushal维护着一个名为SecureDrop的开源举报者平台。该平台已在超过75家媒体机构中部署。举报者通过该平台提交信息时,记者需要在一个物理隔离的、运行特定操作系统的计算机上查看加密文件。解密密钥存储在另一台隔离的计算机中,记者需要通过USB等物理介质传递文件进行解密。整个过程耗时约40-45分钟。
这引发了安全性与可用性之间的权衡问题。对于SecureDrop这类处理敏感信息的项目,始终面临国家级攻击者的威胁。因此,必须在两者之间做出选择,并寻求专业的安全建议。


解决方案:Cubes OS与桌面应用

上一节我们讨论了安全与可用性的矛盾,本节中我们来看看他们是如何通过技术创新来拉近这两者距离的。
团队开发了一款为记者设计的桌面应用程序,目前处于Alpha阶段。该应用部署在一个名为Cubes OS的特殊操作系统上。Cubes OS是一个基于Qubes/Xen构建的安全操作系统,其特点是每个应用程序可能运行在独立的虚拟机中,从而实现类似“气隙”的隔离效果。私钥等敏感信息可以存储在独立的虚拟机中。

这种方法显著提升了安全环境下的应用可用性。该桌面应用使用Debian软件包进行部署,这意味着它运行在Cubes OS内部的一个Debian虚拟机中。
潜在的安全威胁

上一节我们介绍了提升安全性的技术方案,本节中我们来系统地看看在软件供应链中可能存在的各类安全威胁。
以下是几个现实世界中的安全事件示例,它们揭示了不同环节的脆弱性:
- 供应链攻击:华硕公司的签名密钥曾被攻击者窃取,用于签署并分发恶意软件。
- 依赖库投毒:在其他编程语言生态中,出现过核心库被植入恶意软件的情况。在Python生态中,2018年也发生过攻击者发布与知名项目名称相似的恶意包,试图窃取比特币钱包信息。
- 人员威胁:根据爱德华·斯诺登披露的信息,政府人员会通过参加技术会议等方式,尝试获取关键基础设施的访问凭证。
我们可以将这些威胁总结为以下四个主要方面:
- 源代码可能包含恶意软件:攻击者可能入侵开发者电脑并篡改代码。
- 依赖项或源代码被完全替换:强大的攻击者可能替换整个依赖包。
- 二进制文件被恶意软件替换:在下载过程中可能遭遇中间人攻击。
- 网络存储或基础设施被攻陷:托管包的平台服务器本身可能被入侵。
缓解策略:从依赖项检查开始
上一节我们梳理了主要的安全威胁,本节中我们开始探讨如何构建防御体系。首要的一步是严格管理项目依赖。
对于Python项目,通常使用pip和requirements.txt文件管理依赖。一个普通的requirements.txt文件格式如下:
SomeProject==1.4
AnotherProject==1.0.2
然而,为了安全,我们需要更严格的控制。目标是只使用自己构建的二进制包(wheels)。因此,我们使用一个包含哈希校验和的requirements.txt文件:
SomeProject==1.4 \
--hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
这确保了所安装的包必须与指定的SHA-256哈希值匹配。
构建可验证的依赖包流程
上一节我们确定了使用哈希校验来确保依赖完整性,本节中我们深入看看如何自动化地构建和验证这些依赖包。

整个流程通过一个Makefile和几个Python脚本来实现,遵循“不创造新工具”的原则。核心步骤是一个名为build-wheels的构建任务。


以下是构建和同步依赖包的关键步骤:
- 验证签名与哈希:首先运行脚本验证源代码压缩包的GPG签名和SHA-256哈希值是否与预期匹配。
- 下载源代码:使用一个临时的
requirements.txt文件(仅包含源代码压缩包),通过pip命令下载所有依赖的源代码,并强制进行哈希验证。pip download --no-binary :all: --require-hashes -r requirements.txt - 构建二进制包:将下载的所有源代码压缩包在本地构建成wheel文件。
pip wheel --no-index --wheel-dir wheels/ --find-links wheels/ -r requirements.txt - 生成最终需求文件:基于新构建的wheels,生成包含对应哈希值的最终
requirements.txt文件。 - 打包项目:创建项目自身的源代码分发包,其中将包含最终的
requirements.txt文件以及项目维护者的GPG签名。
人工审计源代码

上一节我们自动化了依赖包的构建,但自动化无法判断代码意图。本节中我们来看看如何通过人工审计来确保源代码本身是干净的。
上述流程假设从PyPI获取的源代码是可信的,但这并非绝对。为了确保依赖项源代码没有恶意,SecureDrop项目制定了一条规则:所有依赖项的更新都必须经过人工审计。

他们使用diffoscope工具来对比新版本和旧版本源代码之间的差异。由两位开发者手动审查变更内容,检查其中是否包含:
- 可疑的网络连接代码。
- 不应存在的Base64编码数据。
- 任何可能的后门或恶意逻辑。

只有通过审计的依赖项更新才会被允许。所有依赖项的源代码压缩包和wheel文件的SHA-256哈希值都被记录在一个名为sha256sums.txt的文件中,并使用GPG密钥对该文件进行签名,以供任何人验证。

创建私有包仓库
上一节我们确保了依赖包的安全,本节中我们来看看如何安全地存储和分发这些包。
拥有源代码压缩包和wheel文件后,需要一种方式来托管它们以供使用。在Python生态中,可以简单地使用静态HTTP服务器创建一个私有仓库。
根据PEP 503规范,仓库的结构非常简单:
- 一个顶级的
index.html作为索引页,列出所有项目链接。 - 每个项目目录下有一个
index.html,列出该项目所有版本的文件链接。
例如,项目索引页可能包含:
<a href="/project/SomeProject/">SomeProject</a>
而项目页面则包含具体文件的链接:
<a href="SomeProject-1.4.tar.gz">SomeProject-1.4.tar.gz</a>
<a href="SomeProject-1.4-py3-none-any.whl">SomeProject-1.4-py3-none-any.whl</a>
这些静态文件可以提交到Git仓库,并部署在任何支持HTTPS的静态文件服务器上,从而创建一个受控的、可审计的包源。
打包与部署:虚拟环境与系统包
上一节我们创建了安全的包源,本节中我们来看看如何将整个应用及其依赖打包,以便于在目标系统上安全部署。
直接使用pip安装到系统Python环境是危险的。常见的解决方案是使用虚拟环境。为了部署,可以将整个虚拟环境打包。
SecureDrop项目使用dh-virtualenv工具(一个来自Spotify的开源项目)。它能够将Python虚拟环境打包到Debian软件包中。这样,应用程序的所有Python依赖都被封装在包内,与系统Python环境隔离,并且可以通过系统包管理器进行安装和管理。
一个简化的debian/rules文件配置示例如下:
%:
dh $@ --with python-virtualenv
override_dh_virtualenv:
dh_virtualenv --python /usr/bin/python3.5 \
--setuptools \
--pip-tool pip \
--extra-index-url https://your.private.pypi/simple
实现可重现构建
上一节我们完成了应用打包,本节中我们探讨构建安全的最后一块拼图:可重现构建。
可重现构建意味着从相同的源代码,在任何时间、任何机器上,都能产生完全相同的构建产物(如二进制文件、软件包)。这是验证软件未被篡改的终极手段。
Debian从Stretch版本开始支持可重现构建。通过结合之前的所有步骤——使用哈希校验的依赖、私有仓库、dh-virtualenv打包,并确保构建环境的一致性(如固定的工具链版本),SecureDrop项目实现了可重现的Debian软件包构建。
安全威胁与缓解措施对应总结
在本节课中,我们一起学习了为安全环境构建Python应用的完整方法论。现在,我们来总结一下所讨论的安全威胁及其对应的缓解措施:
| 威胁 | 缓解措施 |
|---|---|
| 源代码包含恶意软件 | 对所有依赖项更新进行人工源代码审计。 |
| 二进制文件被替换 | 存储并GPG签名所有依赖的SHA-256哈希值文件;在构建过程中多次校验。 |
| 中间人攻击 | 全程使用HTTPS;配合SHA-256哈希校验确保下载内容完整性。 |
| 基础设施被攻陷 | 签名密钥离线保存(不接触网络);自托管私有PyPI仓库;实现可重现构建,使任何篡改都无法通过重建验证。 |
这套组合方案提供了真实性(通过GPG签名)、可审计性(所有材料公开可查)和可重现性,共同确保了在安全环境中部署的软件正是开发者所构建的版本,没有掺杂任何恶意内容。
资源与问答
- 项目链接:
- 新闻自由基金会:
freedom.press - SecureDrop项目:
securedrop.org - 示例脚本与Makefile可在相关GitHub仓库找到。
- 新闻自由基金会:
- 问答环节要点:
- 选择Debian包而非Nix/区块链:基于团队现有技术栈和“使用成熟工具”的原则。
- 私有仓库是公开的:为了透明和可审计,但最终用户直接安装的是打包好的Debian包。
- 为不同系统打包:需要为不同发行版(如Debian和Ubuntu)分别构建软件包。
- 处理C扩展:将必要的C库列为构建依赖,目前未遇到重大问题。
- 审计疲劳:承认人工审计压力大,希望社区能探索协作审计的信任模型。


本节课总结:我们深入探讨了在高安全要求下构建Python应用程序的全套实践。核心在于不引入新工具,而是通过严格的人工审计、哈希校验与签名、私有包管理、虚拟环境打包以及追求可重现构建,来层层加固软件供应链的每一个环节。这套方法虽然步骤繁多,但能有效抵御多种高级威胁,为安全关键型应用提供了坚实的保障。
012:发布你的第一个Python包并自动化未来的发布 🚀

在本教程中,我们将学习如何创建并发布你的第一个Python包到PyPI(Python包索引),并探讨如何通过自动化工具来简化后续的发布和维护流程。我们将从最基础的步骤开始,逐步添加更多元数据、依赖管理和自动化测试,最终实现一个完整的、可自动发布的包。
什么是PyPI?📦
PyPI是Python包维护者使用的包存储库。它是Python生态系统的重要组成部分,使得开发者能够轻松地分享和安装代码库,从而促进了整个社区的学习和创新。
随着Python和PyPI的不断发展,发布过程对许多人来说可能显得有些神秘。本教程的目标是证明这个过程其实很简单,每个人都能学会。
什么是Python包?🐍
Python包是你可以部署给其他用户使用的模块、类或函数的集合。下面是一个非常简单的例子:
我们有一个名为mypackage的模块,它包含一个__init__.py文件和一个module.py文件。module.py中有一个简单的函数spam,它总是返回字符串"eggs"。
# mypackage/__init__.py
# 这是一个空文件,用于标识这是一个包
# mypackage/module.py
def spam():
return "eggs"
这个简单的结构足以展示如何部署到PyPI。
第一步:创建 setup.py 文件 📄
为了将包部署到PyPI,我们需要一个setup.py文件。最基本的setup.py只需要四个字段:
name: 包的名称。version: 包的版本号。description: 包的简短描述。packages: 指定包中包含哪些内容。
setuptools.find_packages()是一个辅助函数,它会自动发现当前目录下需要包含的包。
# setup.py
from setuptools import setup, find_packages
setup(
name='mypackage',
version='0.0.1',
description='一个非常简单的示例包',
packages=find_packages(),
)
第二步:本地测试你的包 ✅
在发布之前,我们应该先在本地测试包是否能正常工作。
首先,创建一个虚拟环境并进入。然后,使用pip以“可编辑”模式安装你的包。这种模式允许你在修改代码后无需重新安装。
pip install -e .
安装完成后,打开Python交互式环境(REPL),导入你的包并测试函数。
>>> import mypackage
>>> from mypackage.module import spam
>>> spam()
'eggs'
如果一切正常,说明你的包在本地可以正确工作。
第三步:发布到测试PyPI 🧪
在正式发布到PyPI之前,最好先在测试PyPI上进行演练。首先,安装必要的工具:twine和wheel。
pip install twine wheel
接下来,构建你的包。sdist命令创建源代码分发,bdist_wheel命令创建二进制wheel分发。虽然纯Python包不一定需要wheel,但提供它是一种良好的实践。
python setup.py sdist bdist_wheel
这个命令会在dist/目录下生成分发文件。现在,使用twine将它们上传到测试PyPI。
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
最后,你可以尝试从测试PyPI安装你的包。
pip install --index-url https://test.pypi.org/simple/ mypackage
恭喜!你现在已经成功发布了一个最基础的Python包。


第四步:完善包的元数据 🏷️
虽然最基本的包可以工作,但为了给用户提供更好的体验,我们应该添加更多元数据。
以下是setup.py中可以添加的一些重要字段:
- 作者信息:
author和author_email,让用户知道如何联系你。 - 项目链接:
url,通常是代码仓库的地址。 - 分类器:
classifiers,帮助用户在PyPI上发现和理解你的包。例如,说明开发状态、支持的Python版本、操作系统和主题。
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries',
],
- 许可证:
license,这是最重要的部分之一。没有许可证,许多用户(尤其是企业用户)将无法合法使用你的代码。常见的许可证有MIT、Apache 2.0和GPL。 - 详细描述:
long_description和long_description_content_type,可以从README.md文件读取,为用户提供安装和使用说明。
一个更完善的setup.py示例如下:
from setuptools import setup, find_packages
from os import path
this_directory = path.abspath(path.dirname(__file__))
with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
setup(
name='mypackage',
version='0.0.1',
description='一个非常简单的示例包',
long_description=long_description,
long_description_content_type='text/markdown',
author='你的名字',
author_email='your.email@example.com',
url='https://github.com/yourusername/mypackage',
packages=find_packages(),
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Operating System :: OS Independent',
'License :: OSI Approved :: MIT License',
'Topic :: Software Development :: Libraries',
],
python_requires='>=3.5',
install_requires=[
'requests', # 示例依赖
],
)

第五步:管理依赖和凭据 🔐
上一节我们完善了包的基本信息,本节我们来关注依赖管理和发布凭据。
管理依赖:使用install_requires字段声明你的包所依赖的其他库。这样,当用户安装你的包时,pip会自动安装这些依赖。

install_requires=[
'requests>=2.20.0',
],
管理发布凭据:向PyPI上传包需要用户名和密码。有几种管理方式:
- 交互式输入:运行
twine upload时在命令行输入。 - 使用
.pypirc文件:在主目录创建此文件,存储凭据(注意安全,避免提交到代码库)。 - 使用
keyring库:一个更安全的密码管理器。

第六步:利用模板和自动化 🛠️
随着包的增多,手动编写setup.py和项目结构会变得繁琐。以下方法可以提高效率:
- 复用代码:从你之前的成功项目中复制
setup.py并进行修改。 - 使用Cookiecutter:这是一个项目模板工具,可以一键生成包含
setup.py、文档结构、测试框架等在内的完整项目骨架。 - 参考官方示例:PyPI提供了一个详尽的示例项目,注释了几乎所有
setup.py参数的使用方法。
第七步:自动化测试和发布 🤖
手动执行测试和发布流程容易出错且难以扩展。自动化可以带来一致性、可重复性和团队协作的便利。
自动化测试:我们需要测试包在多个Python版本下的兼容性。可以使用tox或nox等工具。
tox通过.ini配置文件定义测试环境。nox使用Python函数定义任务,更加灵活。
以下是一个noxfile.py的示例,它会在Python 3.5, 3.6, 3.7下运行测试并构建文档:
import nox
@nox.session(python=['3.5', '3.6', '3.7'])
def tests(session):
session.install('pytest', 'mock')
session.install('.')
session.run('pytest', *session.posargs)
@nox.session
def docs(session):
session.install('sphinx')
session.install('.')
session.run('sphinx-build', 'docs', 'docs/_build')
持续集成与自动发布:我们可以将自动化测试和发布流程集成到CI/CD服务中,如CircleCI、Travis CI等。
一个典型的流程是:
- 每次推送代码或创建拉取请求时,CI运行所有测试。
- 当给代码库打上版本标签(如
v1.0.0)时,CI在通过测试后自动执行发布流程(构建、上传到PyPI)。
在CI配置中,你需要通过环境变量安全地设置PyPI凭据。以下是一个简化的CircleCI配置思路:
# .circleci/config.yml
workflows:
version: 2
test_and_deploy:
jobs:
- test_py35
- test_py36
- test_py37
- deploy:
requires:
- test_py37 # 部署前确保测试通过
filters:
tags:
only: /^v\d+\.\d+\.\d+$/ # 仅对版本标签触发部署
branches:
ignore: /.*/
jobs:
test_py37:
docker:
- image: circleci/python:3.7
steps:
- checkout
- run: pip install nox
- run: nox -s tests
deploy:
docker:
- image: circleci/python:3.7
steps:
- checkout
- run: pip install twine wheel
- run: python setup.py sdist bdist_wheel
- run: twine upload dist/*
# 注意:这里需要通过环境变量或上下文提供PYPI_USERNAME和PYPI_PASSWORD


总结 📚
在本教程中,我们一起学习了如何发布Python包:
- 创建基础包:我们从一个简单的模块开始,编写了最基本的
setup.py文件。 - 本地测试:我们学会了如何在本地以可编辑模式安装和测试包。
- 发布到测试环境:我们使用
twine将包发布到测试PyPI进行演练。 - 完善元数据:我们添加了作者、描述、分类器、许可证等关键信息,使包更专业、更易用。
- 管理依赖和凭据:我们讨论了如何声明依赖以及安全管理发布凭据的几种方式。
- 提高效率:我们了解了如何使用模板和复用代码来加速新项目的创建。
- 实现自动化:我们探索了使用
nox进行多版本自动化测试,并利用CI/CD服务(如CircleCI)实现测试和发布的完整自动化流水线。

通过掌握这些知识和工具,你现在可以自信地创建、发布并维护高质量的Python包,为丰富的Python生态系统贡献自己的力量。祝你发布顺利!
013:衡量模型公平性 📊


在本节课中,我们将学习如何衡量机器学习模型的公平性。我们将探讨为什么这是一个复杂的问题,理解影响公平性评估的关键因素,并通过一个案例研究了解这些因素在实践中的体现。最后,我们将介绍一些可用于评估公平性的Python工具。
动机与概述 🎯
机器学习模型可以对人们的生活产生重大影响。信用评分模型可以决定一个人能否购买房屋。广告模型可以影响一个人接收到的工作机会类型。模型甚至可以影响一个人在监狱中待多久。
随着机器学习模型进入这些具有社会影响力的领域,确保模型预测的公平性变得非常重要。然而,定义“公平”的方式有很多种,这使得衡量公平性成为一个棘手的问题。

公平性定义的挑战:COMPAS案例 🤔
一个著名的案例是COMPAS再犯预测模型。该模型用于预测被定罪者获释后再次犯罪的可能性,并广泛用于假释决定。
一个名为ProPublica的新闻机构分析数据后发现,该模型在不同种族群体间的错误率不同。
- 对于白人被告,假阳性率(被错误标记为高风险)约为24%。
- 对于黑人被告,假阳性率约为45%。
- 对于白人被告,假阴性率(被错误标记为低风险)约为48%。
- 对于黑人被告,假阴性率约为28%。

ProPublica据此得出结论,该模型对黑人被告存在偏见。
然而,模型制造商Northpointe公司反驳称,该模型的总体准确率在白人和黑人被告中非常相似,都约为63%。
这就引出了一个问题:这个模型公平吗?哪种衡量方式才是正确的?
随后的学术研究证明,除非模型完美无缺,否则同时满足“错误率平衡”和“总体准确率平衡”这两个公平性定义在数学上是不可能的。
因此,核心问题在于:如何为你的具体问题定义“公平”?这涉及许多细微之处。
影响公平性衡量的三个关键因素 🔍
上一节我们介绍了定义公平性的挑战,本节中我们来看看使问题复杂化的三个关键因素。
1. 不同群体的真实阳性率可能存在合法差异
在某些问题中,不同群体间的真实结果分布本就不同。例如,在预测乳腺癌的诊断时:
- 女性终生患病率约为12.5%。
- 男性终生患病率约为0.1%。
这种真实的、合法的差异会使某些公平性指标失效。一个流行的指标是不同影响,它衡量的是不同群体间被预测为“正面”结果的概率之比。
公式:P(预测=‘是’ | 群体=A) / P(预测=‘是’ | 群体=B)
该比值越接近1,被认为越公平。但在乳腺癌的例子中,由于真实患病率差异巨大,强行让预测概率之比接近1是不合理且不现实的。
2. 数据是对真实世界的有偏呈现
你的数据并非对真实情况的完美反映。数据偏差主要有两种形式:
标签偏差:受保护属性影响了数据标签的分配方式。
例如,一项研究发现,在类似行为问题上,非裔和拉丁裔学生比白人学生更容易被停学或开除。如果使用“是否被停学”作为预测学生问题行为的标签,那么这个标签本身就带有偏见。优化模型与这些有偏标签的一致性,只会延续数据中的偏见。
一个受此影响的指标是机会平等,它比较的是不同群体间的真正率。
公式:TPR(群体=A) - TPR(群体=B)
(其中,TPR = 真正例数 / 实际正例数)
样本偏差:不同群体以不同方式被抽样进入数据集。
例如,对纽约警察局“拦截盘查”政策的分析发现,非裔和西班牙裔被拦截的比例远高于白人。如果基于此数据构建模型,那么数据集中就存在样本偏差,因为个体是否出现在数据中与其群体属性相关。
这会影响像不同影响这类比较分类比率的指标,因为不同群体的抽样基础不同,比较变得不公正。
3. 模型的后果至关重要
模型的用途决定了我们应优先关注哪种错误。
- 惩罚性模型(如量刑辅助):后果是施加惩罚。我们可能更关心假阳性(错误地施加惩罚)。
- 辅助性模型(如福利分配):后果是提供帮助。我们可能更关心假阴性(错误地拒绝帮助)。
核心观点:数学无法替代人的思考 💡
综合以上因素,本教程的核心观点是:你不能仅仅依靠数学公式来确保公平,必须有人来思考模型的伦理影响和具体情境。
最初,人们可能认为“模型是数学,所以它自然是公平的”。这种观点已被驳斥。但现在有一种新的诱惑,认为“我为模型添加一个数学约束(公平性指标),它就会自动变得公平”。这同样不成立。
在应用任何公平性约束或指标之前,你需要思考:在你的具体问题中,公平意味着什么?模型的后果是什么?你的数据反映了怎样的现实?
案例研究:理论在实践中如何体现 📈
前面我们讨论了许多理论上的细微差别,本节中我们通过一个模拟案例来看看它们如何在实践中发挥作用。
该案例使用来自真实咨询项目的数据,特征包括人口统计和社会经济指标,目标是预测个人注册某项国家服务的可能性。我们关注白人和黑人群体间的公平性。
我们通过以下步骤构建实验:
- 创建两个“假设世界”:
- 基础真实值平衡:仅使用原始数据中的白人样本,并随机重新分配种族标签。
- 基础真实值不平衡:使用原始数据,其中白人的注册概率本就更高。
- 在每个“假设世界”中,生成带有已知偏见的数据集:
- 注入样本偏差:对白人和黑人采用不同的抽样概率。
- 注入标签偏差:对白人和黑人使用不同的阈值将概率转化为二元标签。
- 在这些数据集上训练模型,并应用两个公平性指标进行评估:
- 不同影响(比值,理想值为1)
- 机会平等(差值,理想值为0)
实验结果如下:
在基础真实值平衡的假设世界中,结果符合预期。当数据无偏见时,两个指标均未检测到不公平。当注入样本或标签偏差后,指标正确地报告了检测到的不公平。
然而,在基础真实值不平衡的假设世界中,情况变得复杂。即使在没有注入任何偏见、仅反映真实世界差异的数据上,两个指标也检测到了“显著的不公平”。此外,标签偏差在这种情境下更难被准确检测。
案例研究的启示:
在实践中,你只会看到自己数据集上的一个结果(类似于上图中的一个条形)。要解读公平性指标给出的数字,你必须主动思考:
- 你试图建模的问题中,真实结果在不同群体间是否可能存在合法差异?(因素1)
- 你的数据生成过程是否存在标签偏差或样本偏差?(因素2)
- 脱离这些背景,单纯一个“不公平”的指标数值是没有意义的。
衡量公平性的Python工具 🛠️
了解理论后,以下是一些可以帮助你实施公平性评估的Python工具。
1. Equitas
- 描述:由芝加哥大学开发,包含Python库和Web界面。
- 用法:提供数据、指定受保护属性、选择公平性指标,工具会评估你的模型。
- 优点:易于使用。
- 注意:采用非标准的学术许可协议。
2. AI Fairness 360 (AIF360)
- 描述:IBM开发的开源工具包。
- 用法:提供了大量公平性指标、算法和详细的教程笔记本。
- 优点:非常全面,文档丰富。
- 注意:可能过于庞大,依赖较多,生产部署需考虑开销。
3. 模型解释工具
理解模型“为何”做出特定预测,也是评估其公平性的重要部分。你可以探索以下工具:
- LIME
- SHAP
它们有助于识别模型是否依赖于不恰当的特征进行决策。
总结与建议 📝
本节课中我们一起学习了衡量机器学习模型公平性的复杂性。我们了解到:
- 没有放之四海而皆准的解决方案:公平的定义取决于具体情境。
- 必须进行情境化思考:认真考虑你的输入数据、输出结果、模型后果以及你所建模的世界。
- 利用多样化团队:在模型开发和伦理审查中,多样化的视角有助于发现盲点。
- 理解数据并承担责任:数据并非“真相”,即使是,它也是你想要在世界上促成的“真相”吗?构建模型的人需要承担起思考这些伦理问题的责任。
最后记住:公平性指标是强大的辅助工具,但它们不能替代人类的批判性思考。你需要将这些工具与对问题背景的深刻理解结合起来,才能负责任地衡量和管理模型的公平性。


本节课中我们一起学习了:衡量模型公平性的动机与挑战、影响评估的三个关键因素(真实率差异、数据偏差、模型后果)、通过案例研究理解了理论在实践中的应用,并介绍了几种实用的Python评估工具。核心在于认识到,确保公平不仅是一个技术问题,更是一个需要深入情境化思考的伦理与实践问题。
014:P14


在本节课中,我们将回顾 PyCon 2019 周六下午的闪电演讲环节。我们将学习多个主题,包括使用 Qt for Python 创建图形用户界面、如何开始写技术博客、实用的开发者小技巧、以及一些创新的 Python 工具和库。本节内容旨在为初学者提供一系列简单易懂的实用知识。

演讲 1:使用 Qt for Python 创建你的第一个用户界面 🖥️



Qt 是一个跨平台的 C++ 框架,广泛用于开发图形用户界面。现在,Qt 官方支持 Python,使得 Python 开发者也能轻松创建桌面应用。



如何安装与使用

安装 Qt for Python 非常简单,就像安装其他 Python 模块一样:
pip install PySide2


基本应用结构
创建一个基本的 Qt 应用通常遵循以下结构:
- 声明一个应用(
QApplication)。 - 重载一个小部件类(例如
QWidget或QMainWindow)。 - 在窗口中添加标签(
QLabel)、按钮(QPushButton)等组件。 - 将按钮的点击信号(
clicked)连接到一个槽函数(slot),以定义点击后的行为。 - 使用布局管理器(
QLayout)排列组件。 - 显示窗口并启动应用事件循环。


以下是一个简单的代码框架:
import sys
from PySide2.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout

class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建标签和按钮
self.label = QLabel('Hello, World!', self)
self.button = QPushButton('Click Me', self)
# 连接按钮点击信号到槽函数
self.button.clicked.connect(self.on_button_clicked)
# 设置布局
layout = QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.button)
self.setLayout(layout)
def on_button_clicked(self):
self.label.setText('Button Clicked!')

if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
了解更多
想了解更多关于 Qt for Python 的信息,可以访问其官方网站或相关社区。

上一节我们介绍了如何使用 Qt for Python 构建桌面应用。接下来,我们将转换话题,看看如何通过写技术博客来分享知识和建立个人品牌。
演讲 2:如何开始你的软件技术博客 ✍️
写技术博客是分享知识、记录学习和连接社区的好方法。以下是三个帮助你开始的建议。
确定你的目标
在开始之前,明确你写博客的目的至关重要。是为了记录个人学习笔记、撰写教程指南、展示示例代码,还是希望建立个人品牌、连接同行?明确的目标能帮助你保持动力和内容的一致性。应避免以追求名声或财富为主要目的。


创造新颖有用的内容
网络上的信息已经非常丰富,因此尽量创造独特、有价值的内容。例如,分享你在解决一个特定、未被广泛记录的问题时的经验。这样的内容不仅对他人有帮助,也能让你在社区中获得认可。

工具和平台并不重要


在博客创作初期,内容本身比技术平台更重要。你可以选择从简单易用的平台开始,如 WordPress 或 Wix。如果你喜欢更多控制,也可以用 Django 等框架自己搭建。关键是尽快开始写作,而不是纠结于工具。
写博客是梳理和巩固知识的有效方式。接下来,我们将看一个非常简短但极其实用的生活小技巧。
演讲 3:让会议名牌更易读的小技巧 🏷️


在会议午餐时,挂在脖子上的名牌(挂绳)常常会垂到桌子下面,导致对面的人看不到你的名字。
解决方法非常简单:只需在挂绳的顶端打一个小结,缩短其长度。这样,当你坐下时,名牌就能保持在桌面上方,方便他人查看。

这个小技巧能帮助你在社交场合更顺畅地交流。下面,我们将进入更技术性的内容,了解一个能简化数据存储和查询的 Python 库。
演讲 4:XDB:一个基于文件的简单数据库 💾
XDB 是一个类似 BerkeleyDB 的基于文件的数据库。它没有自己的查询语言,而是让你直接使用 Python 来进行数据操作,从而避免了在字节数据和 Python 对象(如字典)之间反复转换的样板代码。


核心特性
XDB 提供了多种数据序列化(如 JSON、MessagePack)和压缩选项。它支持多进程访问,并且其表(Table)接口类似于 Python 字典,但可以处理远超内存容量的大型数据。
基本用法

以下是创建和打开一个 XDB 数据库文件的示例:
import xdb
# 创建数据库
with xdb.Writer('my_data.xdb', ident='my_app', version=1) as w:
table = w.add_table('my_table', serialize='json', compress='zstd')
table['key1'] = {'name': 'Alice', 'age': 30}
# 读取数据库
with xdb.Reader('my_data.xdb', ident='my_app', version=1) as r:
table = r.get_table('my_table')
print(table['key1']) # 输出: {'name': 'Alice', 'age': 30}
XDB 还附带了一些有用的命令行工具,用于检查文件信息和直接与数据交互。
处理数据是开发中的常事。在开发过程中,另一个常见需求是管理项目依赖和环境,接下来我们将看到一个旨在优化此体验的工具。


演讲 5:优化开发者体验的脚本模式 🛠️



在团队开发中,统一的开发环境和简化的流程能显著降低认知负担,让开发者更专注于功能实现。


统一的脚本接口



许多项目采用一套统一的 Bash 脚本目录(例如 scripts/)来标准化常见操作。例如:
scripts/bootstrap:用于一键安装所有项目依赖、配置环境。scripts/help:列出所有可用脚本及其功能。scripts/vendor-install <package>:添加依赖包。scripts/vendor-uninstall <package>:移除依赖包。




优势



这种方法假设团队使用统一的操作系统和硬件环境(如 macOS),从而简化了脚本的编写和维护。它通过提供明确的命令,让新成员能快速上手,并让日常任务(如依赖管理)变得自动化。




通过脚本标准化流程后,代码本身的质量和可维护性同样重要。下一个演讲介绍了一个有趣的实验,它试图从语法层面改变我们编写类的方式。


演讲 6:“无私的 Python”:一个实验性语法糖 🧪
这个实验性项目 selfless 尝试提供一种新的语法来定义类,旨在消除代码中显式的 self 关键字,让代码看起来更像纯函数。
使用方式
使用 @selfless 装饰器,你可以将一个函数定义为“类”,其参数成为实例属性,函数体内的变量操作直接对应实例属性的读写。
from selfless import selfless
@selfless
def Vector2(x, y):
# `x`和`y`直接成为实例属性
def __repr__():
return f'Vector2(x={x}, y={y})'
def add(other):
return Vector2(x + other.x, y + other.y)
def set_x(new_x):
nonlocal x
x = new_x
# 返回公共方法
return add, set_x, __repr__

# 使用
v = Vector2(1, 2)
print(v) # 输出: Vector2(x=1, y=2)
v.set_x(10)
print(v) # 输出: Vector2(x=10, y=2)
请注意:这是一个概念性项目,展示了 Python 元编程的灵活性,并非用于生产环境。
无论是实验性语法还是实用工具,它们的健康发展都离不开可持续的生态。最后一个演讲将介绍一个旨在支持开源可持续性的新倡议。
演讲 7:FAIR OSS:促进开源可持续性的公益公司 🌱
FAIR OSS 是一家新成立的公益公司,旨在可持续地生产和维护可自由共享的开源软件。

核心模式

它连接两方:
- 开源开发者与社区:定义“公平”合作的原则。
- 企业:对遵循这些原则使用开源软件的企业进行认证。


运作方式



FAIR OSS 计划推出类似“公平贸易”的认证等级(如银级、金级)。获得认证的企业可以使用特定商标,展示其以负责任的方式支持开源。认证产生的收入将用于回馈给对应的开源项目和维护者,形成良性循环。

目标


其目标是建立一种社区主导、开发者拥有的可持续模式,将企业的成功与它们所依赖的开源项目的健康更紧密地绑定在一起。




本节课中我们一起学习了 PyCon 2019 周六闪电演讲的多个主题。我们从 使用 Qt for Python 构建 GUI 应用 开始,了解了如何安装和创建基本窗口。接着,探讨了 如何开始技术博客写作,强调了目标、内容和工具的选择。然后,学习了一个让会议名牌更实用的 生活小技巧。之后,我们深入技术工具,认识了简化数据存取的 XDB 数据库,以及通过标准化脚本优化工作流的 开发者体验模式。最后,我们接触了两个前沿思想:一个是通过 selfless 库进行的 实验性语法探索,另一个是支持开源长期发展的 FAIR OSS 公益倡议。这些内容涵盖了从实用技巧到生态建设的广泛视角,希望能为你的学习和开发带来启发。
015:何时重构,何时保持现状 🧑💻


在本节课中,我们将学习如何识别代码中的问题(即“代码异味”),并掌握重构的时机与方法。我们将探讨如何在不改变软件外部行为的前提下,改善代码的内部结构,使其更易于理解和维护。
概述:重构的平衡木
重构是对现有代码进行重组的过程,其核心是在不改变外部行为的前提下,提升代码的可读性与可维护性。这个过程就像走平衡木,需要在“值得进行的更改”和“应该保持现状的部分”之间找到平衡点。
重构的哲学与动机
上一节我们介绍了重构的基本概念,本节中我们来看看驱动我们进行重构的核心理念。

Python之禅为我们提供了很好的指导原则:
- 美胜于丑
- 显式胜于隐式
- 简单胜于复杂
- 可读性很重要
- 应该有一种——且最好只有一种——明显的方式去实现它
- 如果实现难以解释,那就是个坏主意

重构的最终目的是增进理解,无论是对于编写者还是未来的维护者。如果重构使代码变得更复杂,那我们就偏离了目标。
在开始重构前,必须牢记:始终需要一个回滚策略。即使是简单的重命名,也可能对生产环境产生意想不到的影响。一个可靠的测试套件是确保重构安全性的基本工具。
识别代码异味:何时需要重构
代码异味是源代码中可能指示更深层次问题的特征。它们并非绝对的错误,而是提醒我们值得仔细检查的信号。我们将代码异味分为三大类:太长/太复杂、不够有用和糟糕的面向对象设计。
处理“太长/太复杂”的代码

以下是几种常见的“太长”类代码异味及其解决方法。

1. 重复代码
如果你在多个地方编写了相同或相似的代码,这是一个强烈的信号,表明你应该将其提取到一个独立的方法或类中。

2. 过长方法
判断方法是否过长的标准不是行数,而是其复杂性和单一职责。如果你无法为方法想出一个清晰、简洁的名称来描述其功能,它很可能需要被拆分。

3. 过长参数列表
当一组参数频繁地一起出现时,可以考虑将它们封装成一个对象。
# 重构前:参数分散
def process_data(start_time, end_time, user_id, data_type):
...

# 重构后:使用参数对象
class TimeRange:
def __init__(self, start, end):
self.start = start
self.end = end


def process_data(time_range, user_id, data_type):
...

4. 数据泥团
如果你发现相同的几项数据总是在多个地方同时出现,就应该考虑将它们提炼成一个独立的数据结构或类。
处理“不够有用”的代码

上一节我们讨论了如何拆分过长的代码,本节中我们来看看相反的情况:如何压缩或合并那些“不够有用”的部分。


1. 冗余类(懒惰类/投机泛化)
这种情况常发生在过度设计时,创建了许多实际上并未承担足够职责的类。解决方案通常是折叠继承体系或内联类,将功能合并到更有用的类中。
# 重构前:存在无用的父类
class Employee:
def __init__(self, name):
self.name = name
class Engineer(Employee):
def __init__(self, name, team):
super().__init__(name)
self.team = team
# 重构后:将属性提升到父类
class Employee:
def __init__(self, name, team=None): # 团队属性提升
self.name = name
self.team = team
2. 纯数据类
这是“数据泥团”的反面模式。如果一个类仅仅是一堆属性的集合(只有数据,没有行为),那么它可能只是一个数据结构,而非真正的对象。尝试寻找与这些数据相关的行为,并将其移入该类中。
处理“糟糕的面向对象设计”
这是最复杂的一类代码异味,涉及对象之间的不良交互。
1. 发散式变化 & 霰丨弹丨枪式修改
- 发散式变化:一个类因为多种不同的原因而经常被修改。
- 霰丨弹丨枪式修改:每做一次修改,都必须在许多不同的类中做出许多小修改。
这两种异味都表明职责没有良好地集中。解决方法是通过提取类或搬移方法,将相关的变更点集中到一起。
2. 特性依恋 & 不恰当的亲密关系
一个对象过于频繁地访问另一个对象的内部数据。这违反了封装原则。解决方案通常是搬移方法,将操作数据的方法移到数据所在的类中。
3. 过度使用Switch语句
大量的switch或if/elif语句(尤其是在检查对象类型时)是使用多态的好机会。可以考虑用状态模式或策略模式来替代。
4. 过长的消息链
客户端通过一连串的访问(如a.getB().getC().getD())才能获取所需数据。这增加了耦合度。可以通过隐藏委托关系,在中间对象上提供一个方法直接获取最终数据。
5. 拒绝继承
子类继承了父类的所有方法,但其中一些并不需要或并不适用。这通常意味着“组合优于继承”的原则被违反了。考虑用组合替代继承,只包含需要的功能。


Python特有的代码异味与重构技巧


除了通用原则,Python也有一些特有的模式和重构技巧。
1. 过度嵌套的代码
深层嵌套的if/for语句会降低可读性。可以利用Python的迭代器、提前返回(early return)或any()/all()等内置函数来使代码扁平化。
2. 冗余的模板代码
如果你写了一个类,主要就是定义__init__来存储属性,并手动实现__repr__等方法,那么你可能在重复造轮子。
# 重构前:手动编写模板代码
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
# 重构后:使用数据类
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
3. 异常处理与业务逻辑混杂
try...except块应该专注于错误恢复,而不应包含大量的业务逻辑。将核心逻辑提取到独立的方法中。
4. 重复的设置与清理代码
如果代码中频繁出现配对的设置和清理操作(如打开/关闭文件),考虑使用上下文管理器(with语句)。
重构实战:一个案例演示
让我们通过一个简化案例,看看如何将复杂的条件逻辑重构得清晰。假设我们要根据季节和库存决定餐厅的菜单。
初始代码(复杂嵌套):
def get_menu(season, ingredients):
if season == "summer":
if "bread" in ingredients and "sauce" in ingredients:
return "pizza"
elif "cheese" in ingredients and "bread" in ingredients:
return "grilled_cheese"
# ... 更多嵌套判断
elif season == "winter":
# ... 另一套复杂的嵌套判断
# ... 其他季节
这段代码难以阅读和扩展(例如,增加“是否点酒”的条件会使它更糟)。
重构步骤:
- 引入菜单项列表:将选项从条件判断中抽离,定义为数据结构。
- 提取方法:将“判断某道菜是否可用”的逻辑提取成独立方法,使主逻辑更清晰。
- 引入类封装:创建
SeasonalMenu类,将季节、可用食材和选择逻辑封装在一起,便于测试和扩展。
重构后的代码结构更清晰,职责分离,添加新条件时只需修改特定部分,避免了“霰丨弹丨枪式修改”。
关键要点:
- 不要边重构边添加功能:先重构,稳定后再添加新功能,或者先添加测试再重构。
- 小步前进:每次做小的、可验证的更改,比一次性大规模重写更安全、更可控。
- 追求进步,而非完美:重构的目标是持续改进,而不是一步到位达到理论上的完美设计。


重构策略与时机


如何安全地进行重构?
- 测试驱动开发:编写测试 -> 运行测试(失败)-> 重构 -> 运行测试(通过)。
- 扩展与收缩模式:当需要保持向后兼容时,先并行运行新旧两套代码,逐步将调用方迁移到新接口,最后删除旧代码。

何时应该重构?
- 当你后悔之前没有这样做时。
- 当团队中没人能理解某段代码时。
- 当代码的坏味道让后续修改变得异常困难时。
- 当添加注释也无法清晰说明代码意图时(应将“理解”转化为“清晰的代码”)。
何时不应该重构?
- 如果添加清晰的注释或文档就能解决问题。
- 如果只是为了炫技或证明自己。
- 如果代码已经糟糕到需要完全重写的地步。
总结与核心建议
本节课中我们一起学习了重构的核心思想、常见代码异味的识别以及具体的重构方法。
核心建议:
- 信任你的直觉:如果你觉得代码“有味道”,那通常值得深入查看。
- 牢记目标:重构是为了提升可理解性和可维护性。如果结果更复杂了,那就走错了方向。
- 制定策略:在动手前,想好回滚方案、测试策略以及如何保持兼容性。有计划地小步前进。
- 善用工具:熟悉你的IDE的重构功能(如重命名、提取方法等),它们能极大提高安全性和效率。
- 保持沟通:尤其是对团队共享代码的重构,确保他人了解你的意图和改动。

重构是一项需要持续练习的技能。从识别小处的代码异味开始,逐步培养你的“代码嗅觉”,你就能在平衡木上走得越来越稳,创造出更健壮、更优雅的软件。
本教程内容主要思想来源于马丁·福勒的经典著作《重构:改善既有代码的设计》,推荐深入阅读。
016:安德鲁·戈德温 - 地形、艺术、Python 和 LIDAR


📖 概述
在本教程中,我们将跟随安德鲁·戈德温在 PyCon 2019 的演讲,学习如何利用 Python 处理地形数据(如数字高程模型和激光雷达点云),并将其转化为实体艺术品。我们将涵盖从数据获取、处理到通过激光切割、3D打印和数控铣削进行制造的完整流程。本教程旨在让初学者理解核心概念,并看到 Python 在数字艺术和地理空间数据处理中的强大应用。

🗺️ 1:地形数据简介
几个世纪以来,人类一直试图以越来越高的精度绘制我们的世界。从最初的粗略测量到现代的激光技术,我们获取地形数据的方式发生了巨大变化。

航天飞机雷达地形任务
在20世纪90年代,航天飞机雷达地形任务通过航天飞机上的雷达天线,首次获得了覆盖全球的完整地球表面地图。它提供了地球上几乎所有地方的高度值,精度大约为30米。这种数据在宏观尺度上表现良好,能显示岛屿、山脉和山谷。

公式: 数据精度 ≈ 30米
激光雷达技术
如今,我们使用激光雷达技术。激光雷达通过光进行探测和测距,其精度可以达到厘米级。这些数据通常由飞机搭载的设备采集,能够构建极其详细的地图,尤其适用于城市等区域的精确测绘。

核心概念: 激光雷达点云是原始数据,由一系列三维坐标点构成。
代码表示:
# 一个简化的点云数据结构示例
point_cloud = [
(x1, y1, z1, intensity1),
(x2, y2, z2, intensity2),
# ... 数百万个这样的点
]
🖼️ 2:数字高程模型
上一节我们介绍了原始地形数据的来源,本节中我们来看看最常用的数据形式——数字高程模型。
数字高程模型(DEM),在游戏领域常被称为高度图,本质上是一个大型的二维数组或位图。图中的每个像素(或数组中的每个单元格)代表该地理位置的海拔高度。


上图中,左侧是伦敦的DEM,右侧是火山湖的DEM。白色代表最高点,黑色代表最低点。
在Python中,我们可以将DEM视为一个二维数值数组进行处理,这为后续的数据操作和艺术创作奠定了基础。
🔨 3:制造技术概述
获取并理解了地形数据后,下一步就是利用这些数据制造实体模型。以下是几种主要的制造方法。
激光切割
激光切割是最简单的制造方法之一。激光切割机通常只需要一个定义切割路径的SVG文件。制作流程是:将DEM数据转换为等高线,生成SVG文件,然后送入激光切割机进行切割,最后将切割出的部件组装粘合。
3D打印
3D打印可以创建出细节更丰富的实体模型。然而,3D打印机需要完全密封的三维模型文件(如STL格式),而不能直接打印二维的高度图表面。因此,需要将DEM数据转换为具有厚度和封闭底部的三维网格。
数控铣削
数控铣削是一种减材制造工艺,通过旋转的刀具从一块原材料(如铝块)上切削出模型。它同样需要三维模型文件来生成刀具路径。这种方法适合制作坚固、精致的金属模型。
🐍 4:Python数据处理流程
现在,我们来看看如何用Python将原始数据转化为可用于制造的模型文件。整个过程涉及多个关键步骤。
以下是处理激光雷达点云数据并生成3D打印模型的主要步骤:
- 数据获取与转换:获取原始的点云数据(
.las等格式),并使用专业工具(如lastools或PDAL库)将其处理并转换为规则网格的数字高程模型(DEM)。 - 数据清洗与调整:
- 剔除异常值:去除因水面、玻璃反射造成的错误高点。
- 高度裁剪:将模型底部统一裁剪到某个基准面(如海平面),以避免过深的凹陷导致打印问题。
- 垂直夸张:为了艺术效果和视觉清晰度,通常会将高度(Z轴)放大1.5到2倍。
- 平滑处理:对数据进行平滑,消除激光雷达数据固有的微小噪点。
- 生成三维网格:将二维的DEM数组转换为三维三角形网格。这包括:
- 为每个数据点生成顶面多边形。
- 创建侧面墙壁,使模型具有厚度。
- 生成底部平面,封闭整个模型。
- 输出模型文件:将生成的三维网格写入标准3D打印格式,如STL文件。
代码示例(概念性):
# 伪代码,展示流程
dem_array = load_dem(‘dem_data.tif’) # 加载DEM数据
cleaned_array = clean_data(dem_array) # 清洗数据
exaggerated_array = exaggerate_height(cleaned_array, factor=2.0) # 高度夸张
smoothed_array = smooth_data(exaggerated_array) # 平滑
stl_model = convert_to_3d_mesh(smoothed_array, thickness=10.0) # 转为3D网格
write_stl_file(stl_model, ‘output_model.stl’) # 写入STL文件
性能建议: 在处理大型数值数组时,强烈建议使用 NumPy 库,它可以极大地提升运算效率,比使用纯Python列表快得多。
🧩 5:处理不规则形状
在为国家公园制作模型时,我们遇到了新挑战:模型边界不再是简单的正方形,而是不规则的多边形轮廓。
这需要结合地理信息系统(GIS)技术。我们可以使用QGIS等工具,将国家公园的矢量边界轮廓叠加到全国的DEM数据上,从而“裁剪”出只属于公园范围的高度数据。
在数据处理代码中,需要修改算法以识别这些不规则边界,并沿着真实轮廓生成模型的侧壁,而不是默认的正方形边框。这涉及到处理DEM中的“无数据”区域(通常用如-99.99的值填充)。
⚠️ 6:挑战与注意事项
在将地形数据转化为艺术品的实践中,会遇到一些技术挑战和需要权衡的地方。
地图投影问题
地球是球体,而地图是平面。将球面地图投影到平面时必然会产生扭曲。不同的投影方式适用于不同地区。在艺术创作中,我们可以选择视觉上更美观的投影,而非追求绝对的地理精度。
模型优化
由DEM直接生成的三维网格可能包含数百万个多边形,导致文件巨大,许多3D软件难以处理。通常需要在Blender等软件中进行网格简化优化。理想情况下,这个优化步骤可以集成到Python处理流程中。
制造限制
不同的制造工艺有其限制。例如,数控铣削使用的微小刀具非常脆弱,容易折断;3D打印大型模型需要很长时间。在项目规划时需要考虑这些实际约束。
🚀 7:未来展望
地形数据艺术创作领域仍有广阔的发展空间,未来的可能性令人兴奋。
- 更易得的激光雷达:随着自动驾驶技术的发展,个人用、低成本激光雷达扫描仪可能变得普及,这将允许我们扫描和创建室内、洞穴等更复杂空间的精细模型。
- 流程自动化:将模型优化、刀具路径(G-code)生成等步骤更深入地集成到Python脚本中,实现从数据到制造指令的更自动化流程。
- 探索新形式:结合更多样的数据源(如卫星影像、地质数据)和制造技术,创造出信息更丰富、形式更多样的地形艺术作品。
✅ 总结
本节课中我们一起学习了如何利用Python桥接地理数据与实体艺术。我们从地形数据(DEM和激光雷达点云)的基础知识出发,逐步讲解了数据清洗、转换、三维网格生成的完整流程,并探讨了激光切割、3D打印和数控铣削等不同制造技术的应用。我们还了解了处理不规则边界、地图投影等挑战,并展望了该领域未来的发展方向。希望本教程能启发你利用Python探索数据可视化和数字制造的世界。
相关资源:
- 演讲中提到的项目代码和链接可在原演讲视频末尾找到。
- 用于处理地理空间数据的Python库推荐:
rasterio,geopandas,PDAL,laspy。 - 用于3D模型处理的库:
numpy-stl,trimesh。


017:什么是PLC以及如何用Python与之通信

在本节课中,我们将学习可编程逻辑控制器(PLC)的基本概念,了解它在工业自动化中的核心作用,并探索如何使用Python与PLC进行通信。课程内容基于PyCon 2019的演讲,旨在为初学者提供一个清晰、实用的入门指南。
P17:1:什么是PLC? 🏭
上一节我们概述了课程内容,本节中我们来看看PLC究竟是什么。
PLC,全称可编程逻辑控制器,是一种专门为工业环境设计的数字计算机。它们被用于控制各种机械和生产线,例如工厂中的机器人、风力涡轮机的叶片角度调节、建筑机械、主题公园设施、洗车场以及公共交通系统。可以说,在现代城市中,你距离一个PLC通常不会超过50英尺。
PLC的核心作用是作为软件世界(比特和字节)与物理世界(原子和电子)之间的接口。它通过电线连接到“现场设备”,这些设备包括传感器(将物理现象转化为电信号)和执行器(将电信号转化为物理动作)。
PLC的工作遵循一个基本的“输入-处理-输出”循环:
- 读取输入:PLC从连接的传感器读取电信号,并将其转化为变量,存储在一个称为“过程映像”的内存区域中。
- 执行逻辑:PLC运行用户编写的程序(逻辑),根据输入变量计算并生成输出变量。
- 写入输出:PLC将输出变量从过程映像转化为电信号,发送给执行器(如电机、风扇),从而影响物理世界。
对我们而言,第二步——即可编程逻辑部分——是最有趣的,因为这是我们能够施加影响的地方。

P17:2:PLC编程入门 ⚙️
上一节我们介绍了PLC的基本功能,本节中我们来看看如何为PLC编程。


你不能使用普通的文本编辑器(如VSCode或PyCharm)来为PLC编程。相反,你必须使用PLC供应商提供的专用编程软件。PLC领域最古老、最通用的编程语言之一是“梯形逻辑”。
梯形逻辑得名于其程序结构看起来像一个梯子。它源于继电器控制逻辑时代,当时人们通过物理连接继电器来实现控制逻辑。在梯形逻辑中:
- 左垂直轨 被视为“带电”的。
- 右垂直轨 是“中性”的。
- 水平线 代表电流的路径。
- 触点 代表条件(如“如果开关按下”),放置在水平线上。
- 线圈 代表输出动作(如“打开灯”),放置在水平线的末端。
程序执行时,可以想象电流从左向右流动。如果通过触点形成的路径是导通的,则电流会流到线圈,从而激活输出。
以下是一个简单的梯形逻辑概念示例,用ASCII形式表示:
|---[ ]---( )---|
其中 [ ] 代表一个常开触点(条件),( ) 代表一个线圈(输出)。这相当于一个简单的逻辑:“如果条件为真,则激活输出”。
这种语言已经存在了约50年,它的一大设计目标是易于调试和维护,因为最终维护系统的往往是工厂的技术人员和电工,而非最初的程序员。
P17:3:使用Python与PLC通信 🐍
上一节我们了解了PLC的编程方式,本节中我们来看看如何用Python与运行中的PLC交互。
PLC通常需要相互通信或与上位机系统通信,因此发展出了多种工业通信协议。Python可以通过各种库来支持这些协议,从而实现与PLC的数据交换。
一个广泛使用的协议是 Modbus。它的工作方式是为PLC内存中的变量分配数字地址,其他设备(包括运行Python的计算机)可以通过网络读写这些地址。


以下是使用Python pymodbus 库与PLC通信的一个基本示例:
from pymodbus.client import ModbusTcpClient

# 1. 连接到PLC
client = ModbusTcpClient('192.168.1.100') # PLC的IP地址
client.connect()
# 2. 读取保持寄存器(例如,从地址0开始读取5个值)
result = client.read_holding_registers(address=0, count=5)
if not result.isError():
timer_durations = result.registers # 这是一个包含数值的列表
print(f“读取到的计时器时长:{timer_durations}”)
# 3. 写入单个寄存器(例如,将地址0的值改为10)
client.write_register(address=0, value=10)
# 4. 关闭连接
client.close()

通过这种方式,Python脚本可以监控PLC的状态,或动态修改PLC的运行参数(如调整交通信号灯的计时),从而将实时控制(在PLC内完成)与高级数据分析、优化算法或用户界面(在Python中实现)结合起来。
P17:4:Python支持的工业协议 📡
上一节我们以Modbus为例介绍了Python与PLC的通信,本节中我们系统性地看看Python生态还支持哪些工业协议。
工业领域存在大量通信协议,以下是部分在PyPI上已有Python包实现的协议:
特定供应商协议:
pycomm3(Allen Bradley)python-snap7(Siemens S7)pyads(Beckhoff ADS)melsec(Mitsubishi Melsec)omron(Omron FINS)
开放标准协议:
- Modbus (
pymodbus): 最古老、最通用的协议之一。 - OPC UA (
opcua,asyncua): 现代、强大、支持数据发现和复杂数据类型的协议。 - EtherNet/IP (
pycomm3,cpppo): 常用于罗克韦尔等设备。 - MQTT (
paho-mqtt): 轻量级的发布/订阅协议,在物联网中广泛应用。 - PROFINET (需通过套接字和
ctypes实现,或使用网关)。
选择建议:
对于初学者,从 Modbus 或 OPC UA 开始是不错的选择,因为它们文档丰富、库成熟。如果你的设备支持 OPC UA,强烈推荐使用,因为它提供了更现代和友好的API。


P17:5:树莓派能当PLC用吗? 🤔
上一节我们探讨了通信协议,本节中我们来讨论一个常见问题:廉价的树莓派能否替代PLC。

直接回答是:在大多数严肃的工业场合,不能。原因如下:
- 环境耐受性:PLC设计用于极端环境(高温、低温、潮湿、振动、电磁干扰),而树莓派没有。
- 可靠性保障:PLC制造商提供长期的供应链保障和可靠性认证,树莓派没有。
- 实时性:PLC运行确定性的实时操作系统,能保证代码在精确的时间内执行。通用操作系统(如Linux)无法提供这种硬实时保证。
- 电气特性:PLC具有隔离的输入/输出电路、ESD保护,并能直接连接工业电源和信号。树莓派需要额外的、可能不可靠的扩展板。

但是,存在一些项目和产品,旨在为树莓派赋予PLC的能力:
- 软件层面:如
CODESYS for Raspberry Pi和OpenPLC,它们通过实时补丁或协同处理的方式,在树莓派上提供实时运行环境。 - 硬件层面:如
Revolution Pi,它使用树莓派的计算模块,但重新设计了满足工业要求的周边电路和外壳。
因此,结论是:对于学习、原型开发或非关键的家庭/实验室应用,树莓派配合相关软件可以模拟PLC的功能。但对于要求可靠性、安全性和耐用性的实际工业部署,仍应选择专业的PLC硬件。
P17:6:下一步与资源 📚
在本节课的最后,我们为你提供一些继续学习的路径和资源。
如果你想动手尝试:
- 使用模拟器:搜索“PLC simulator online”找到免费的梯形逻辑在线模拟器进行练习。
- 购买学习套件:一些PLC供应商(如西门子、艾伦布拉德利)提供低成本的学习套件。也可以在eBay上寻找二手PLC设备。
- 探索开源项目:深入研究
OpenPLC或opcua这样的开源项目。


推荐资源:
- 协议库:从PyPI安装
pymodbus或opcua,阅读其文档和示例。 - 树莓派PLC:查看
CODESYS for Raspberry Pi或Revolution Pi的官网。 - 社区:参与工业自动化、物联网相关的论坛和开源社区。
总结:
本节课中我们一起学习了:
- PLC 是工业自动化的核心控制器,连接数字与物理世界。
- PLC通常使用梯形逻辑等专用语言编程。
- 我们可以使用Python和多种工业通信协议(如Modbus, OPC UA)与PLC通信,实现监控、数据采集和高级控制。
- 虽然树莓派在严格意义上不能替代工业PLC,但通过特定项目可以在某些场景下实现类似功能。

希望本教程为你打开了工业自动化与Python结合的大门。记住,关键在于理解PLC的实时控制角色与Python的高级数据处理角色之间的互补关系。
018:欢迎与会议事务 🎉



在本节课中,我们将学习 PyCon 2019 大会的欢迎致辞和重要会务信息。内容涵盖行为准则、会场布局、活动安排以及参会注意事项,旨在帮助你顺利、安全地享受这次技术盛会。
欢迎致辞与感谢 🙏

早上好。欢迎来到 2019 年 PyCon。
我代表 PyCon 2019 工作人员,感谢所有让这次会议成为可能的志愿者。感谢 Python 软件基金会承担此次会议的财政责任。感谢所有前来分享工作的演讲者。感谢支持本次会议的赞助商。感谢所有参会者。


行为准则与安全环境 ⚖️

PyCon 致力于为每个人提供一个安全和积极的体验。

所有工作人员、与会者、演讲者、赞助商和志愿者在任何 PyCon 活动中都必须遵守我们的行为准则。请熟悉该行为准则以及事件处理程序。

这些文件链接在 us.pycon.org 网站的“关于”部分。

如果你担心有人违反了 PyCon 行为准则,鼓励你进行报告。如果你不确定某事件是否属于违规,我们仍然鼓励你报告。组织者乐于接受那些不采取行动的报告,以创造一个更安全的空间。
以下是报告方式:
- 电子邮件:
picon-us-report@picon.org - 短信或电话:
1-218-62-8572 - 在会议期间,寻找穿着特定颜色 T 恤的事件响应者。

我鼓励你保存这些联系信息。

会场导览与设施 🗺️
欢迎来到 PyCon。你位于俄亥俄州克利夫兰的会议中心。会场相当大,我们将逐步介绍。


无障碍设施 ♿
我们将为今年所有的演讲提供实时字幕。每个演讲室的中央区域保留了轮椅通道。会议符合 ADA 标准。如果你有任何顾虑或需要额外的便利,请告诉工作人员。

主要活动区域

你现在位于 展览厅 A。在接下来的三天里,这里将举行全体会议和主题演讲。此外,这个房间还将举办快速演讲(Lightning Talks)。

展览厅 B 和 C 是餐饮区和赞助商展位所在地。周日之前的所有餐饮将在此处提供。你可以在指定网址找到菜单和成分信息。赞助商展位今天和明天开放。

在展览大厅内,还有 创业者展位。今天和明天会有一些新兴公司在此展示。请注意,这些公司会更换。

分组讨论室
分组讨论室位于 H 室(舞厅),距离主会场有一定距离。如果你计划去那里,请预留足够时间。

今年有五个主题,共 95 场会议。大多数演讲将会被录制,并在会议后在线提供。
开放空间与社区活动

我们有一支开放空间(Open Spaces)房间队伍。这些房间从早上 8 点开放到晚上 11 点,供任何人自行组织聚会式活动。

你可以到注册处旁边的公告板上发布自己的开放空间,或查看已有的内容。今年新增了在线查看公告板内容的功能,网址为屏幕上显示的 URL。

PyCon 孵化器项目 🚀
25C 和 26 A/B/C 房间是 PyCon 孵化器项目的家。该项目旨在为任何人提供在 PyCon 上实现创意的机会。
今年有三个新活动加入:
- Laus Park(西班牙语演讲):全天在 25C 房间用西班牙语进行的演讲。
- Python 艺术年:一个迷你艺术节,重点关注叙事、表演和视觉艺术。今晚在 26 A/B/C 房间举行。
- 维护者峰会:旨在为项目维护者和关键贡献者建立实践社区。今天在 25C 房间举行。

此外,明年的指导冲刺 将于明天在 25C 房间进行,这是一个帮助新手参与开源项目的活动。


社交与礼仪建议 🤝


PyCon 占用了会议中心的大部分空间。没有规定你必须参加每一个活动。考虑花时间结识新朋友,或与老朋友交流。
一个改善社交体验的建议是 “吃豆人规则”:当你站在一群人中时,始终留出空间让一个人加入你的圈子。这有助于创造一个身体上欢迎的空间。
其他便利设施与本地信息 🏙️
会场内有三个 性别中立的洗手间 和两个 母婴室。如果你需要休息或专注于工作,我们在 24 号房间设有一个 安静室,请在此空间内保持安静。

克利夫兰市中心有普拉提拍卖会和 PyCon 晚宴等活动。晚宴是需要门票的活动,请携带你的票券参加。如果你不打算参加,请将票转给他人,以减少食物浪费。

重要会务提醒 📋

会议资料
- 有一本印刷的会议程序册。
- 你可以通过访问
pikon.us/guidebook使用移动指南应用。

Python 软件基金会筹款
Python 软件基金会正在进行筹款活动。如果你有能力,请考虑贡献。


趣味跑步与交通
如果你注册了 PyCon 趣味跑步和步行,请注意活动时间和交通安排。明天早上市中心有跑步活动,可能会造成拥堵,请预留额外时间。
赠品包与 T 恤
所有与会者将收到一个赠品包,内含一个能够运行 Python 的 Circuit Playground Express 开发板。
- 旅行通过安检时,请将此开发板从包中拿出来并向检查人员说明。
- 考虑在周日的闭幕主题演讲中带上它,你可能会受到启发立即开始编程。
你的 T 恤 可以在指定地点领取。请注意,由于面料较薄,建议在公众场合穿着前先试穿。如果对衬衫有任何顾虑,请告诉我们。


日程变更
有一些日程变更。你的打印程序可能没有更新。请花时间检查会场前的公告板,或使用移动指南应用获取最新信息。主要变更有:
- 星期六在格兰德舞厅 B 中,由菲利普·詹姆斯讲授的“使用 Python 的 Mastodon”替代了原定日程。

摄影与隐私
今年现场有专业摄影师。如果你不希望被拍到,可以去注册处领取一个带有红色横幅的证件套。佩戴此证件套,摄影师会避免拍到你,我们在后期也会确保不使用你的照片。

社交媒体
如果你想在会议上通过社交媒体引起工作人员或组织者的注意,可以发推 @picon。如果你想分享关于会议的信息,请使用标签 #picon2019。


本节课中,我们一起学习了 PyCon 2019 的核心会务信息,包括行为准则、会场布局、特色活动以及多项实用提醒。掌握这些信息将帮助你更从容地参与会议,专注于技术交流与社区互动。祝你在 PyCon 2019 有一段充实而愉快的经历!
021:Python软件基金会社区报告与社区服务奖 🏆

在本节课中,我们将了解Python软件基金会(PSF)的运作、其对社区的回馈方式,以及如何表彰那些为Python生态系统做出卓越贡献的成员。我们将看到PSF如何通过资金、活动和支持来推动Python语言的发展,并认识一些获得社区服务奖的杰出贡献者。
赞助商致辞与社区支持 💼
大家好。
我是Brent Klimitz,来自Capital One。我是一名开发人员。这是我参加PyCon的第四年。我在PyCon看到许多新面孔,这显示了社区的伟大,以及过去几年它的成长,坦率地说,这门语言的进步。
你可能会理解或不理解为什么Capital One会成为赞助商,为什么我们在这里。坦率地说,我们热爱这门语言。它为我们在Capital One的创新提供了动力,尤其是在我们的开源项目中,如Cloud Custodian,以及我们的客户面对的项目如Eno。我们支持社区,想要看到这门语言的进步。

这些也是我来到Capital One的很多原因,因为我们真的非常热爱这个社区,并想要支持它,看到它向前发展。我们非常感谢社区在我们的生态系统项目中给予我们的帮助。我们为成为该社区和PyCon的主要赞助商而感到自豪。

并期待在未来的PyCon上见到你们。

PSF执行董事致辞与社区回馈 🤝
欢迎Eva,她将更新Python软件基金会的情况,并颁发社区服务奖。

我叫Eva,我是Python软件基金会的执行董事。如果你对Python社区的更新感兴趣,请关注我和PSF的Twitter。
现在让我们谈谈回馈。
我们在PyCon和类似的活动中聚集,是因为机会。这些面对面的活动为我们提供了学习、网络、合作、与朋友见面、交新朋友、教学、指导等众多机会。通过这些机会,我们促进了Python的发展,使我们的社区更强大。
在某种程度上,我们回馈给了Python。

通过注册参加,你在回馈社区,赞助会议,讲授教程,进行主题演讲,这些都在回馈社区。所有这些回馈的形式都帮助我们的社区成长。Python软件基金会是Python及其社区背后的非营利组织。
PSF的年度影响与资金分配 📊

PSF已成长为一个通过其项目回馈社区的组织。
今年,我们发布了第一份年度影响报告。在这里,我们看到报告中的一小部分,显示了2018年的项目服务费用。大约75%的费用用于举办PyCon。我们第二大费用是用于我们的拨款项目,总计占16%。
随着PSF的持续增长,我们也希望我们的资助项目能够增长。我们的资助资金用于支持其活动和全球各地的地区社区。我们希望继续支持这些活动和社区,因为它们提供了我之前提到的那些有影响力的机会,不仅在这里,还有其他地方。

PSF的未来目标与筹款方式 🎯
除了为我们的社区提供支持外,我们还希望增加PSF的支出预算,以资助Python、教育项目和开发冲刺(DevSprints)等事务。我们希望有足够的资金帮助Python达到下一个水平。拉塞尔周五的主题演讲让我深有感触,因为我觉得他提到了PSF可以支持Python以保持可持续发展的所有方式。
在PSF,我们倾听社区的需求,并希望能够满足其中的很多需求。当然,我们无法满足所有需求,这不是重点。但为了支持核心开发,我们需要更多资金来做到这一点。
捐赠是让我们能够做到这一点的方式。几天前,PSF启动了今年的第一次筹款活动。在这个房间里,大多数人的生活都以某种方式受到了Python的影响。我希望大家现在都能想想Python社区给你们带来的那些机会。
考虑到这一点,我希望大家考虑回馈Python,以便它能继续对世界产生积极影响。
以下是今年支持PSF的两种方式:
- 一种方式是通过捐赠按钮捐款,你可以通过
pikon.us/psf找到它。 - 第二种是通过我们与JetBrains的合作。如果你通过我提供的链接购买PyCharm许可证,这些购买的所有收益将用于PSF。
我们的筹款活动将于5月22日结束。请考虑给予支持。
PSF的团队与可持续发展 👥
PSF的增长部分体现在做出增强我们可持续性的决策,以便我们能够继续支持我们的社区和员工。我们理解多样化收入来源的重要性,这样我们就不再单靠PyCon来维持PSF。
此外,我们决定需要至少保留一年半的运营成本作为财务储备。除了财务责任外,我们还关注了员工的可持续性,以免我们的“公交车因素”降到1。我们最近雇佣了更多人来处理PyCon和我们的会计团队。当我在2011年开始时,只有我们两个人。现在我们有八个人。
我想借此机会把大家请上台,因为在座的每个人都应该看到PSF背后所有工作的团队。看到名字、电子邮件和聊天很不错,但把这些名字与面孔联系起来更好。PSF团队,快上来。
所以,在这几年你们在大会上看到的这个友好的面孔,尤其是在过去几年,不仅是2018年和2019年PyCon的大会主席,而且Ernest还是PSF的基础设施总监。Ernest帮助支持我们的社区满足基础设施需求和问题。此外,Ernest管理所有PSF拥有的基础设施,如pikon.org,帮助维护pikon.org和us.pikon.org以及更多其他网站。
Phyllis Dobbs两年前加入我们,现在正在领导我们的会计团队。Phyllis帮助我们确保在我们非营利组织的财务持续增长时有重要的政策和程序到位。
Jackie Augustin去年9月加入我们,现在是PyCon的经理和首席组织者。我相信你们中的许多人已经与Jackie就注册问题进行了互动,今后你们将看到她参与更多的工作。

Betsy Walaschowski自2015年以来一直在PSF工作。Betsy在提供赞助和筹款支持方面表现出色。今年,Betsy在PyCon赞助方面做得非常出色,她帮助了所有在展览大厅中与您互动的赞助商,解决他们的后勤和需求。

Joe和Laura都在今年早些时候加入了我们。Joe和Laura都加入了我们来帮助会计团队。我们非常感激他们在PyCon和PSF的财务方面提供的帮助,例如财务援助,甚至在今年的飞行员拍卖会上。欢迎加入团队,Joe和Laura。
现在,Herpikizer,请上前。现在我快要哭了。Herpikizer在PSF工作了12年。Herpikizer负责建立PSF的会计系统,并帮助铺就了PSF今天的发展道路。Herpikizer今年将退休,我们都要对他为PSF多年来所做的一切表示衷心的感谢。谢谢你,Herpikizer。
感谢大家与我同台。

社区服务奖颁奖环节 🏅

现在让我们给予社区成员一些认可。
社区服务奖每季度颁发,通常同时颁发给两位Python开发者。这些奖项是PSF对那些帮助加强和改善Python生态系统的贡献者的辛勤付出给予认可的一种方式。
让我们欢迎马里奥·克雷西罗上台。马里奥于2018年获得社区服务奖,以表彰他在PyCon ES的领导和组织工作。Pylandenium和PyCon的查理轨道。他的工作在推广Python的使用和促进西班牙、拉丁美洲和英国的Python社区方面发挥了重要作用。
接下来是查克·伍迪·诺赫图库。查克不幸无法参加今年的PyCon,但查克在2019年获得社区服务奖,以表彰他在促进Python在尼日利亚社区的成长和对PSF拨款工作组的贡献与研究的奉献。
接下来,我想欢迎亚历克斯·盖诺尔上台。亚历克斯于2018年获得CSA,以表彰他对Python和Django社区以及Python软件基金会的贡献。亚历克斯曾在2015至2016年担任PSF董事。他是基础设施工作人员,贡献于遗留PIPI和下一代仓库,并通过压缩404图像帮助减少遗留仓库的不安全性和带宽成本。
现在让我们欢迎马里奥上台。2018年第三季度奖项授予玛丽亚,以表彰她对CPython的贡献、提高Python核心团队工作流程的努力,以及在我们社区内增加多样性的工作。此外,她作为Pike Cascades的共同主席和PyCon的指导式速配的共同组织者,帮助传播Python的成长和多样性。
现在让我们欢迎玛雅·桑切斯·米兰达上台。2018年第四季度奖项授予玛雅,以表彰她作为2019年Pike on Charlie的主席和创始成员,以及组织墨西哥Python Day和Puebla Django Girls的工作。
接下来我们欢迎约翰·罗亚。2018年第四季度的第二个奖项授予约翰·罗亚,以表彰他作为哥伦比亚Pike大会的创始人和主席的工作。此外,约翰在哥伦比亚的Python推广方面发挥了重要作用,尤其是在Django Girls研讨会中。

2019年第一季度奖项授予斯特凡·贝克内尔,表彰他作为两个重要Python项目的主要开发者和维护者的工作。sython和lxml。不幸的是,斯特凡今年无法与我们同在。
现在让我们欢迎Eric Ma上台。2019年第一季度社区服务奖授予Eric,因为他在担任财务援助共同主席和今年的主席期间,始终超越自己的职责。此外,Eric在程序成员方面也表现出色,已经做了好几年。对于那些获得了演讲者资助或财务援助的人来说,见到Eric时一定要感谢他的奉献。

这就是我们今年的颁奖环节。我们社区中有许多优秀的贡献者,这是一种很好的方式来表彰他们的工作。如果你想提名某人参加未来的社区服务奖,请把提名发送给我们。邮箱是 psf@python.org。
总结与致谢 🙏
谢谢你,Eva。也感谢大家的辛勤工作。我们能为获奖者再来一轮掌声吗?

Eva对PSF员工说了很多,但没有太多提及自己。Eva为了确保Python软件基金会和Python的运作所投入的努力和工作是有目共睹的。所以我们来一轮掌声,仅仅为了Eva。
本节课中我们一起学习了Python软件基金会(PSF)的核心作用、资金来源与分配、其支持社区发展的具体方式,以及社区服务奖如何表彰杰出贡献者。我们看到了一个健康的开源社区背后,需要资金、组织和无数志愿者的共同努力来维持其活力与可持续发展。
022:周日闪电演讲 - PyCon 2019

在本节课中,我们将学习PyCon 2019周日闪电演讲中的几个核心分享。内容包括在企业内部推广Python的最佳实践、赋能教育的非营利项目、有趣的Python技术实验以及活跃的社区平台。我们将逐一解析这些实践背后的理念、方法和工具。
章节 1:企业内部的Python推广:辅助工程模型 🏢
上一节我们概述了本次课程的内容,本节中我们来看看乔纳森·比德尔在Wayfair公司推广Python的独特方法——“辅助工程”模型。
我的名字是乔纳森·比德尔。我在Wayfair领导一个名为开发加速的团队。今天我将和大家谈谈我们在公司内部传播Python及其最佳实践的一种方式。
Wayfair是一个专注于家具和家居装饰的电子商务网站。公司规模较大,拥有1400万种产品和1640万客户。公司内部有2300名技术人员。我的任务是让任何软件团队都能直观且轻松地使用Python解决问题,并以Python平台团队的形式实现商业目标。
我们早期考虑的是,如何确保新团队以正确、符合Python习惯的方式使用Python。我们希望通过引导,让他们了解那些可能需要数年经验才能自然领悟的“Pythonic”内容。
我们认为,拥有一个良好的开端故事非常重要。虽然可以进行大量培训或推荐书籍,但我们担心这无法达到理想效果。因此,我们提出了“辅助工程”模型。
这个模型类似于嵌入式工程。我们与团队一起工作,但有两个明确目标:
- 帮助团队取得成功,实现其所需的MVP或商业目标。
- 改变团队,为他们引入Python最佳实践。
参与我们项目的团队可以通过项目README文件中的特定徽章来识别。
在项目开始前,需要进行一些准备工作。我们坚持不强迫任何团队,而是希望他们主动寻求合作,并认为我们提供的价值足以让他们主动联系。我们确保与软件团队、辅助工程师和产品负责人就共同目标达成一致,这些目标包括商业目标、代码覆盖率、黑盒测试或自动化代码检查流水线等。

我们采用为期三个月的工作周期。研究表明,形成一个新习惯大约需要两个多月,这正是我们帮助团队建立良好编码习惯所需的时间。短暂的介入可能无法让习惯固化,因此我们花费大量时间来强化这些实践,努力让对我们来说很自然的事情也成为团队的“第二天性”。


在项目期间,成为团队的一部分至关重要。我们不是外部顾问,而是与团队共同朝着相同目标努力,一起参加会议,共同承担困难的工作。我们绝不独自解决项目中最棘手的问题,因为那会剥夺团队学习和理解解决方案为何被选中的机会。每当遇到难题时,我们都会与团队成员结对编程,确保知识传递。


我们非常重视反馈。每周我们都会与团队进行回顾,讨论辅助工程参与的进展、项目中顺利与不顺利的方面,以及我们在项目中的角色是否有效和健康。我们鼓励团队提供批判性反馈,以便我们了解问题并加以解决。

学习是双向的。我们并非只是作为专家向团队灌输知识。我们从合作中获得大量宝贵信息。例如,我们无法切身体会今天首次学习Python或使用我们平台的感觉,但通过与新开发者坐在一起工作,我们能直接看到他们因文档不清而产生的困惑。
我们将这些反馈纳入工程师20%的自由时间中。所有参与辅助工程的工程师每周有一天可以处理这些信息,利用一周中获得的知识,主动去改善平台,例如修复不直观的设计。这个过程是自我导向的,没有固定流程,只需专注于平台改进。
项目结束六个月后,我们会跟进团队,以确保我们带来的知识能够真正留存。我们观察到,不仅合作团队变得更强大,相邻的团队也开始采纳和传播这些最佳实践,“Pythonic”的理念开始在内部复制。对此模式的需求非常高,我们甚至不得不拒绝一些合作请求,因为平台团队的工程师资源有限。这从侧面证明了我们工作的价值。
这个过程也促成了许多平台改进。通过与团队紧密合作,我们获得了关于哪些工作良好、哪些需要改进的大量优质反馈,并据此优化了平台。
我叫乔纳森·比德尔。我在Wayfair工作。
章节 2:社区赋能:CodeR Dreams的教育项目 📚
上一节我们探讨了在企业内部推广Python的工程实践,本节我们将视线转向社区,看看布里安娜·卡普兰如何通过非营利组织CodeR Dreams,为资源匮乏的学生赋能。
大家好,我叫布里安娜·卡普兰,我是CodeR Dreams的创始人。我们是芝加哥的501(c)(3)非营利组织。我们为资源匮乏的高中学生提供服务,教他们计算机科学,以便他们能够构建推动社区积极变革的应用。


我们可以达成两点共识。第一,计算机思维很重要。根据EdSurg的一项研究,88%的K-12教师认为计算机科学对学生为未来工作做好准备至关重要。正如Python社区所知,STEM教育不仅仅是为了培养科学家、工程师或数学家。但现实是,只有20%的学生实际接受了相关教育。关于计算机科学重要性的信念与学校中的实际教学之间存在脱节。
这些问题主要体现在四个方面:
- 缺乏教师:全国超过一半的公立学区报告在招聘和留住合格且有效的STEM教师方面面临困难。在服务于大量非裔美国人和西班牙裔学生的学区中,这一比例上升到90%。
- 缺乏标准化课程。
- 缺乏设备。
- 缺乏资金:以伊利诺伊州为例,它拥有全国最不公平的学校资金系统,个别学校被低估了24亿美元的资金。
作为一个社区,我们需要投资于那些资源最少的学校。这就是我创办CodeR Dreams的原因。
CodeR Dreams的每个学生小组都从一个问题开始:你属于哪个社区?它有什么伟大和美丽之处?又存在什么问题?学生们提出可以解决这些问题的应用构思,然后在为期一年的项目中构建这些应用。他们学习从构思、用户体验设计、Python编程到产品交付的完整流程,并向社区展示成果。
今年秋天,我们将提供三条不同的学习路径:数据科学、Web开发和物理计算,以创造社会福利。

我们认为以下几点至关重要且与众不同:
- 在学生所在之处找到他们:几乎所有表现不佳的芝加哥高中都位于南部最贫困的人口普查区附近,而大多数STEM项目却在市中心,导致最需要的学生无法参与。我们在学生所在的地方满足他们。
- 关注最需要的学校:资源丰富学校与资源匮乏学校之间存在巨大差距。
- 让学生接触科技中的所有机会:我们的学生有机会担任产品经理、设计师、程序员、销售和市场营销等角色,总有一些东西能引起所有学生的共鸣。
- 赋能学生创造他们想要看到的改变:我们的学生构建的应用程序对他们当地社区产生积极影响。

如果你或你的公司有兴趣赞助或捐款,请通过coderdreams.org联系我们。

章节 3:技术探索:用Python连接《我的世界》 🎮
上一节我们了解了通过教育赋能社区,本节我们来看一个轻松有趣的技术探索——丹尼尔如何用Python与《我的世界》游戏进行深度交互。

我来自这里(指Python社区)。我的女儿喜欢《我的世界》,我也喜欢。通过研究,我发现了用Python的力量来改变你的游戏世界的想法,这似乎是个好主意。
通常的过程是这样的:有一个《我的世界》服务器的Java版本,它提供了一个基于Java的插件框架。还有一个由Python UK社区朋友开发的优秀Python客户端,可以很好地将对象等内容导入游戏。
但我的问题是,这主要是单向的。你可以输入东西,但我真正想做的是能对游戏事件做出反应,比如玩家移动、方块变化等。游戏暴露了非常丰富的API。
于是我想,能否用一些现代Python技术来实现。我们发现可以做一件事:编写一个WebSocket适配器,使用游戏本身的传输协议。有一个优秀的库叫Trio WebSockets,可以用来构建异步客户端。

实现的范围包括:
- 编写Java端插件,用于获取游戏事件。
- 序列化和反序列化一堆Java API对象。
- 编写Python模块来隐藏所有这些细节,提供易于使用的API。


假设我们有一些看起来像这样的代码,用于注册回调并响应玩家移动(例如,在玩家身后放置方块,实现“光轮”效果):


# 示例:响应玩家移动事件
def on_player_move(player, new_position):
# 在玩家旧位置放置一个方块
place_block(player.previous_position, block_type=“GLOWSTONE”)
print(f“Player {player.name} moved to {new_position}”)


# 注册事件回调
game_events.on(‘player_move’, on_player_move)


架构是:游戏与服务器通信,服务器再与Python客户端通信。目前这个项目处于概念验证(Proof of Concept) 阶段,但它确实可以工作。Python客户端可以发送消息,Java端也可以向连接的客户端发送消息。





章节 4:社区建设:PySlackers 线上社区 💬



上一节我们体验了Python在游戏中的趣味应用,本节我们将回归社区,了解一个庞大的线上Python开发者社区——PySlackers。


早上好。我将快速介绍一下PySlackers,因为每个人的生活中都需要更多的工作空间。Slack有一个很棒的工作区切换界面,你应该利用它,加入PySlackers。


PySlackers是一个Python Slack社区,已存在大约三年。我在去年十二月加入,今年二月成为了管理员之一。我加入PySlackers是因为我每天做Python工作,但并非每天都能体验到这样的社区氛围,而我在PySlackers找到了。

以下是关于PySlackers的一些信息(截至演讲时):
- 成员数量:21,352名成员(并且还在增长)。
- 频道数量:79个频道。我们以深思熟虑的方式创建新频道,鼓励使用现有频道,仅在必要时创建新频道。
- 消息公开性:90%的消息是公开的。我们倾向于不使用线程。
- 消息留存:由于我们使用免费版Slack,消息大约只留存一周。

我们每周大约有150名新用户。特别需要指出的是,我们在UTC+7到UTC+14(以及UTC-10)时区的覆盖不足,这些时区的用户在寻求帮助时可能比较安静。如果你懂Python并想帮助他人,且位于这些时区,请加入我们。
我是Chaim Kirby。如果你想和我聊天,你必须加入PySlackers。


章节 5:工具创新:Conda包转换为Wheel格式 ⚙️

上一节我们加入了一个活跃的线上社区,本节我们将关注一个能够提升开发效率的工具创新——将Conda包转换为标准的Python Wheel格式。
安东尼和我在这里谈论“Call of Forge”,这是一个社区驱动的包仓库模式。我们实际的讨论是关于“重新发明轮子”。有趣的是,我们最初的标题是“超越轮子”,后来意识到更好的标题是“进行中的工作”。

关于“Call of Forge”的一点信息:它试图成为你所需的所有软件包的一站式商店,支持Windows、Mac、Linux、ARM等多种平台。如果你需要用一个工具支持多个平台和大量包,“Call of Forge”就是为你准备的。它非常庞大,拥有超过10万个构件,总计超过450GB,分布在6000多个feed中,由超过1000名维护者管理。

我们在这里谈论“轮子”(Wheel),并向你介绍CondaPress。CondaPress是一个将Conda包转换为Wheel格式的新工具。你可以在GitHub的conda-forge组织下找到它(conda-press)。它是一个相当新的项目。

CondaPress试图解决在Conda环境中混合使用不同来源包时常见的ABI(应用程序二进制接口)不兼容问题。有些人可能想使用与主渠道不同的构建版本或更新的ABI进行打包。此外,Conda有很多包还没有Wheel格式,CondaPress尝试将它们移植过来。

现在,我将进行一个演示,展示如何使用CondaPress为Python 3.8构建一个本地的Wheel索引。

演示的核心命令是使用conda-press工具,指定Python版本和平台,对一个Conda包(及其依赖)进行转换:
# 示例命令:为 linux-64 平台的 Python 3.8 转换 conda-forge 的包
conda-press --platform linux-64 --python 3.8 some-package
这个过程会获取Conda包的tar归档文件,提取元数据并将其重新打包成Wheel能理解的格式,移动所有文件到正确位置,而无需重新编译。
转换完成后,会生成一个包含所有依赖包Wheel文件的本地目录。你可以将pip指向这个本地索引,并告诉它不要使用PyPI,然后正常安装这些包,例如安装Python 3.8本身:
pip install --no-index --find-links ./local_wheel_index python=3.8
这适用于所有包。这意味着你可以通过pip,利用从Conda包转换来的Wheel,创建一个干净的环境。

章节 6:全球Python会议巡礼 🌍
上一节我们了解了一个实用的包格式转换工具,本节我们将放眼全球,快速浏览一系列即将举行的区域Python会议,这是Python社区活力线下体现。
以下是来自全球各地Python会议的简短公告:
- PyCon CZ:在斯特拉瓦举行,设有PyData专题,欢迎前来进行“数据挖掘”。
- PyCon UK:在加的夫举行。会议所有利润将直接用于CPython核心开发者的冲刺活动。
- EuroPython:7月8日至14日在瑞士巴塞尔举行。这是一个非常精彩的会议。
- PyOhio:夏季在俄亥俄州哥伦布市举行。CFP开放至5月12日,注册免费。
- DjangoCon AU:CFP开放,涵盖从测试、贡献到数据可视化、Django等广泛主题。
- PyCon Africa:首届会议!将在加纳阿克拉举行。CFP开放至6月1日。
- PyBay:8月在旧金山湾区举行。为期两天,包含研讨会和演讲。
- PyCon Mexico:8月29日至31日在巴亚尔塔港举行。提案征集本月底结束,设有英语和西班牙语双轨道。
- EuroSciPy:9月2日至6日在西班牙毕尔巴鄂举行。提案征集至5月12日。
- PyCon Colombia:9月在波哥大举行。
- PyGotham:10月4日至5日在纽约市举行。提案征集开放至5月12日。
- PyCon Spain:10月在马拉加举行。享受美食、好天气和海滩。
- PyCon Balkan:10月3日至5日在塞尔维亚贝尔格莱德举行。
- PyCon DE & PyData Berlin:10月9日至11日在柏林举行。
- North Bay Python:11月1日至3日在加利福尼亚州佩塔卢马举行(葡萄酒乡)。
- PyCon Canada:11月16日至19日在多伦多举行。CFP开放。
- PyCon Colombia 2020:明年2月7日至9日举行。
- PyTexas:2020年5月16日至17日在奥斯汀举行。
- PyCon Argentina:明年在布宜诺斯艾利斯举行,规模很大,免费参加。
- PyCarolinas:正在筹划中,呼吁社区帮助启动。

本节课中我们一起学习了PyCon 2019闪电演讲中的多个精彩片段。我们从企业内部的“辅助工程”模型看到了如何系统化地推广Python最佳实践;通过CodeR Dreams了解了如何用技术教育赋能弱势社区;探索了用Python与《我的世界》交互的趣味技术实验;认识了庞大的线上社区PySlackers;了解了提升包管理效率的工具CondaPress;最后巡礼了充满活力的全球Python会议。这些内容共同展现了Python社区在实践、教育、创新和连接方面的多样性与活力。
023:PyCon 2019 小组讨论 🐍


在本节课中,我们将学习 PyCon 2019 上 Python 指导委员会小组讨论的核心内容。我们将了解 Python 治理模式的转变、指导委员会的职责,以及社区成员如何参与 Python 的未来发展。



开场与介绍 👋

大家好,我是 Nryk Fake,来自 Facebook 西雅图的生产工程师。Facebook 是 Python 和 PSF 的白金赞助商。我们大量使用 Python,不仅用于配置管理,也用于像 Instagram 这样的产品。我们很自豪能赞助 PyTorch、Pyre 和 Monkey Type 等开源项目,它们分别提升了性能、类型检查和代码分析能力。感谢 Python 组织者和 PSF 让这次会议如此成功。
会议与社区公告 📢
上一节我们介绍了演讲者,本节中我们来看看会议的组织者宣布的重要事项。
以下是本次 PyCon 2019 的一些关键提醒:
- 行为守则:如需举报,可使用提供的联系信息或寻找穿着特定衬衫的工作人员。
- 特殊房间:母亲休息室在 17 号和 18 号房间;安静房间是 24 号房间。
- 展览大厅:已重新配置为海报展示和招聘会,会议结束后立即开始。
- 演讲安排:下午有三场演讲;开放空间开放至晚上 11 点。
- 闭幕会议:包含 PSF 社区更新、颁奖和最终主题演讲,于下午 3:10 在本房间举行。
- 冲刺开发(Sprint):将在会议结束后持续数日,闭幕会议后将举行 Sprint 介绍会。
- PSF 筹款:可通过
picon.us/psf参与。
指导委员会成员介绍 🧑💻
现在,让我们欢迎首届 Python 指导委员会上台。这个小组负责 Python 语言和解释器的治理。本次讨论由 PSF 执行董事 Eva 主持。
以下是各位成员的自我介绍:
- Barry Warsaw:我因 Guido 1994 年的世界巡回演讲而爱上 Python。见证这门语言和社区过去 25 年的发展堪称奇迹,它改变了技术格局和许多人的生活。
- Brett Cannon:我因改进一个不完美的“Python 食谱”配方而开始贡献,并于 2002 年加入。我常说我“为语言而来,为社区而留”,并从社区成员那里学到了很多。
- Carol Willing:我认为 Python 是人民的编程语言。我参与社区是为了从技术角度学习,并帮助核心团队更好地反映现实世界中 Python 的多样用户。
- Guido van Rossum:我创造了 Python 并将其开源。在担任了近 30 年的 BDFL(终身仁慈独裁者)后,我经历了倦怠,决定让社区建立新的治理结构。现在,我作为指导委员会成员之一继续参与。
- Nick Coghlan:我最初使用 Python 来测试 C++ 硬件驱动。这门语言让我能专注于解决问题而非琐碎细节。参与核心开发是关于连接人们思考方式与计算机能力的迷人过程。
治理模式的转变 🔄
上一节我们认识了委员会成员,本节中我们来看看 Python 治理模式的核心变化。
Guido 对比了 BDFL 模式与指导委员会模式的不同:
- BDFL 模式:最终决策和责任集中于一人,压力巨大。每个 PEP(Python 增强提案)都需要其批准。
- 指导委员会模式:决策责任分散给五位经社区投票选出的受信任专家。指导委员会将大多数决策委托给特定领域的专家(PEP 委托人),自身则负责监督和必要时进行干预。这建立了不同的责任关系,并有助于培养下一代领导者。
指导委员会的关注领域 🎯
新的治理模式已经确立,本节中我们来看看指导委员会当前关注的一些具体领域。
- PEP 581 与问题追踪器迁移:该 PEP 提议将问题追踪器从 Roundup 迁移到 GitHub。指导委员会对此持开放态度,但尚未做出正式决定。他们正在评估可行性,并探讨与 PSF 合作引入项目经理以确保平稳迁移。
- 打包生态系统的改进:指导委员会希望澄清 Python 打包权威机构、打包工作组和 PSF 之间的角色。未来的改进重点可能包括发布者体验(如提供发布暂存区)以及持续的安全性、可访问性增强。
- PEP 流程的演进:PEP 流程旨在以结构化文档辅助决策。在新模式下,“委托决策”成为首选方式,旨在让社区专家更多地塑造 Python 的未来,保持语言和社区的活力。
- Python 2 的退休:Python 2 将在 2020 年后停止维护。指导委员会计划可能聘请项目经理来处理沟通和细节(如文档更新),并分享从大公司迁移中学习到的最佳实践。商业支持选项将会存在,但免费支持将终止。
- 促进核心开发的多样性:指导委员会鼓励所有人参与贡献。成为核心开发者无需是 C 语言专家,关键是关心 Python 并愿意学习。社区应创造包容的流程和工具,降低贡献门槛,并通过第三方库生态吸引新人。

问答与社区互动 ❓
在小组讨论的最后,委员会成员回答了现场和在线提问。
以下是部分问答摘要:
- 语言最大的缺口? 成员们认为语言本身很健全,但解释器性能(如何让 Python 更快)和现代化(如引入类似 JavaScript 的源映射以改进调试)是未来重点。
- 如何应对核心开发者倦怠? 改善工具(如迁移问题追踪器)可以减轻负担。社区能做的最大帮助是保持友善,提供建设性反馈。网络上的负面言论会被看到并影响开发者。
- 如何开始成为核心开发者? 访问
devguide.python.org查看开发指南,了解流程。找到感兴趣的领域,积极寻求社区导师的帮助。

总结 🏁
本节课中我们一起学习了 PyCon 2019 上 Python 指导委员会的关键讨论。我们了解到 Python 的治理已从 BDFL 模式转变为由五人指导委员会领导的社区驱动模式。委员会关注于基础设施改进(如问题追踪器)、打包生态、PEP 流程优化,并积极推动 Python 2 的退休计划。他们强调社区包容、友善沟通对项目健康至关重要,并鼓励所有人,无论背景如何,都能参与到 Python 的未来建设中来。Python 的成功依赖于每一个社区成员。
024:最终发言与会议结束 🎤

在本节课中,我们将学习 PyCon 2019 大会闭幕式的主要内容,包括后续活动安排、社区成果公告以及对 Python 社区未来的展望。我们将以清晰的结构整理发言内容,帮助你了解如何参与和贡献于这个充满活力的技术社区。
会议后续活动安排 📅

上一节我们介绍了课程概述,本节中我们来看看大会结束后的具体活动安排。发言者首先提醒与会者注意几个即将举行的活动。
- 闭幕晚宴:当晚 6 点 30 分在大湖科学中心举行。持有门票者请准时出席,若无法参加,请将门票转让他人以避免食物浪费。
- 冲刺公告环节:晚宴后,将预留约15分钟清场,随后由 Cushal 主持“冲刺行”环节。以下是该环节的具体内容:
- 项目负责人可简短介绍其冲刺项目。
- 对参与冲刺感兴趣的人可以聆听并选择意向项目。
- 冲刺入门研讨会:下午 5 点在 25C 房间举行。该研讨会将提供关于冲刺的基本概述及其运作方式。
- 正式冲刺日:从周一到周四举行,期间将提供咖啡和午餐。


社区成果与公告 🏆
在了解了后续活动后,我们来看看本次大会取得的一些重要社区成果。

首先,关于 PyLadies 慈善拍卖的结果:2016年筹得 $16,000,2017年 $21,000,2018年 $30,000,而 2019年筹得了 $41,000。此外,Python 软件基金会(PSF)决定将总额补充至 $45,000。发言者建议社区成员可以开始为明年的拍卖储蓄或考虑捐赠物品。
其次,一个重要的新资源被推出:全新的 PyCon.org 网站。该网站旨在帮助用户找到全球各地离自己更近的 PyCon 活动。组织者可以通过提交 CSV 文件到指定仓库来添加自己的活动,从而共同维护这个活动中心。
注意:请直接访问
PyCon.org,而非www.pyCon.org,因为后者存在 TLS 覆盖问题。

PyCon 的意义与社区精神 ❤️
接下来,发言者分享了其对 PyCon 核心价值的思考。他提到,PyCon 不仅仅是日期和事件的集合,其真正意义在于人与人之间的连接。

通过一次社交媒体调查,他收集了社区成员对 PyCon 的看法。一致的反馈表明:与社区成员面对面相处的时间,加深、拓宽并常常创造了支持个人与职业发展的关系网络。这种互动没有固定形式,这也是 PyCon 包含众多活动且会期较长的原因之一。
发言者强调了“Hatchery”项目的重要性,它支持 PyCon 和社区随着时间不断演进与发展。
放眼全球:参与本地社区 🌍

认识到能够亲临现场是一种特权后,发言者将视角转向更广阔的全球社区。Python 社区远不止与会的 3200 人,例如2018年开发者调查就收到了来自150多个国家的18000份回复。
由于 PyCon 本身规模有限,确保社区增长不依赖于单一大会至关重要。发言者鼓励所有与会者:

- 在返回家乡后,寻找并参与本地的 PyCon、聚会小组或其他社区活动。
- 支持这些活动,担任志愿者,甚至考虑组织活动。
- 这些本地活动提供了 PyCon 无法常年持续提供的连接与成长机会。
展望未来与致谢 👏
最后,发言者正式介绍了 PyCon 2020 及 2021 年的新任主席 Emily,并宣布未来两届大会将在于美国匹兹堡举行:
- PyCon 2020:2020年4月15日至23日
- PyCon 2021:2021年5月12日至20日
随后,大会向所有为本次活动付出努力的组织与个人致以了诚挚感谢,名单涵盖了从 PSF 工作人员、各委员会主席、志愿者团队到所有演讲者、讲师和展示者。
发言者以对全体与会者的感谢作为结束:“没有你们,我们的社区就不可能存在。” 并期待2020年在匹兹堡再会。
总结 📝

本节课中我们一起学习了 PyCon 2019 闭幕式的主要内容。我们梳理了大会后的冲刺活动安排,了解了 PyLadies 拍卖取得的优异成绩和新上线的全球 PyCon 信息平台。更重要的是,我们探讨了 PyCon 超越技术分享的深层价值——即构建人与人之间的支持网络,并认识到参与和支持本地化社区活动对于 Python 生态全球健康发展的重要性。最后,我们展望了未来两届大会的举办信息,并对所有构建此次盛会的贡献者表达了敬意。
025:事前评估与事后评估教程 🛡️


在本教程中,我们将学习两种强大的工具——事后评估和事前评估。它们能帮助我们系统性地处理项目中的失败,将令人恐惧的“事故”转化为宝贵的学习机会,从而降低未来风险。无论你是工程师、数据科学家还是项目经理,这些方法都能为你所用。

为什么失败如此可怕?😨

失败之所以可怕,有几个显而易见的原因。它会让公司损失金钱,占用你和团队的时间。外部用户也可能受到影响,耗费他们的时间和金钱,导致他们不满。此外,失败还带有巨大的情感负担。如果你觉得自己对失败负有责任,很容易感到尴尬和羞愧,觉得自己辜负了团队、公司或用户。这种情感负担使得公开谈论失败变得非常困难。
然而,失败并不完全是个人的责任。错误总是在特定的环境中发生。接下来,我们将探讨导致失败的系统性因素。

理解失败的系统性根源 🔍

失败的发生与个人所处的环境密切相关。如果你在一个团队中工作,可能会面临时间压力。团队中存在正式或非正式的激励与规范,影响着你的工作重点和节奏。此外,团队是否具备成功所需的专业知识也至关重要。
还有一些流程元素值得关注,这些是你可以施加一定控制的部分。例如:
- 测试:在代码上线前,你们进行何种测试?
- 自动化:流程的自动化程度如何?是否存在容易跳过或出错的手动步骤?
- 文档:代码和发布流程是否有良好的文档?糟糕的文档是否容易引入Bug?
- 项目管理:是否有时间管理或问题追踪?你是否清楚项目进度?
- 审查:代码或方法在上线前是否有人审查?
认识到这些系统性因素,是迈向“无责”文化、共同改进的第一步。
事后评估:从失败中学习 📝
上一节我们探讨了失败的环境因素,本节中我们来看看如何通过事后评估来系统性地从失败中学习。
事后评估的核心理念是:个人会犯错,特别是在高压环境下。 因此,我们需要以团队的方式思考,建立能够捕捉和缓解问题的系统。无责的事后评估正是这样一个工具,它能降低情感风险,让我们将事件视为学习机会。
什么是事后评估?
广义上说,事后评估是一个结构化流程,用于:
- 记录事件经过。
- 识别根本原因。
- 找出未来应采取的预防或缓解措施。
尽管它常见于站点可靠性工程(SRE),但同样适用于数据科学、咨询或任何可能“失败”的领域。其交付成果是一份详细描述事件、根本原因和后续行动的文档。
“无责”原则至关重要
“无责”意味着会议的重点是理解事件的系统性根本原因,而不是指责个人。目标是让团队能够改进,而不是追究责任。每个人都对帮助团队良好运作负责,但没有人需要对具体事件负责。
即使看似是“一个人的错”,也应考虑其行动背后的信息、培训和背景。个人的持续表现问题应与经理单独讨论,而非事后评估会议的重点。
一个具体案例:发布有缺陷的代码

为了让概念更具体,我们来看一个数据科学领域的案例:发布了一个有缺陷的代码库,导致内部所有相关工作失败。

会议流程如下:
- 确定参会者:邀请直接参与事件的小团队(如开发者、协助者),必要时可包括受影响方代表(如客户成功经理)。
- 使用模板:会前填写事后评估模板的主要部分。
- 指定协调者:由未直接参与事件的人主持会议,确保讨论高效、聚焦。
- 确认时间线:所有人就事件的基本事实达成一致。
- 时间:事件发生和持续的时间段。
- 摘要:事件如何发生、如何被检测到。
- 影响:对用户造成了何种影响。
- 解决方案:如何解决的,包括尝试过但未成功的方法。
- 分析根本原因:讨论并确认导致事件的深层系统性问题。在上述案例中,根本原因是没有在与生产环境匹配的环境中进行充分测试。
- 总结优点:肯定团队在事件处理中做得好的地方(如快速沟通、提供临时解决方案),这有助于降低情绪温度。
- 识别改进点:明确哪些环节出了问题,需要重点关注。
- 反思“幸运”之处:思考这次侥幸避免但未来可能引发更严重问题的环节。例如,案例中幸运的是这仅是内部发布,且有一位备用维护者可协助。
- 制定行动项:针对根本原因和改进点,制定具体的、可执行的改进措施,并分配负责人。
- 案例中的行动项:更新发布清单,确保在匹配生产环境的环境中测试;规定必须有至少两位维护者可用时才可发布。
- 提炼经验教训:总结可应用于本项目及其他项目的一般性经验。
- 跟进与分享:跟进行动项,并将文档分享给团队甚至公司,作为知识积累。
通过这样的流程,团队不仅能解决当前问题,还能建立应对未来类似事件的流程(如“遇严重Bug立即回滚”),实现持续改进。
事前评估:在失败发生前预防 🚀
上一节我们学习了如何在失败发生后进行复盘,本节中我们来看看如何主动出击,在项目开始前就识别并预防潜在风险,这就是事前评估。
事前评估的核心思想是:在项目启动或早期阶段,主动预测可能出错的地方,并制定应对策略。 这就像为项目进行一次“预演式”的风险排查。
为什么要进行事前评估?
当领导层对项目热情高涨时,团队成员可能不敢提出潜在的担忧。事前评估会议的目的就是鼓励大家公开讨论这些担忧,将其转化为团队的宝贵资产。这样做可以:
- 降低团队焦虑,增强提出问题的信心。
- 揭示跨职能团队中特有的盲点(如技术人员不了解产品顾虑,反之亦然)。
- 确保团队在构建“正确的东西”,并为成功做好准备。


一个具体案例:复杂的Flask数据应用
以一个涉及工程师、数据科学家、产品经理的跨职能Flask应用项目为例,团队面临不确定性、时间压力和沟通挑战。
会议流程如下:

- 确定参会者:邀请来自不同部门的广泛利益相关者(工程师、数据科学家、产品、销售等)。
- 头脑风暴风险(20-30分钟):鼓励大家畅所欲言,提出项目可能失败的各种方式。
- 工程师可能担心:应用性能慢、部署困难。
- 数据科学家可能担心:ETL流程问题、模型效果差。
- 产品/销售可能担心:没人想用这个工具。
- 归类风险:将大量想法归纳为少数几个(如5个)主要风险类别。
- 例如:应用性能、安全漏洞、时间表延误、主要功能缺口、用户采纳度低。
- 评估风险:与会者对每个风险类别从1到3打分,评估其发生可能性(P)和影响严重程度(I)。
- 计算优先级:计算每个风险的平均可能性与平均影响的乘积:
风险值 = P * I。按风险值从高到低排序。- 例如:“功能缺口”可能被评为高风险(可能性高、影响大),而“安全漏洞”可能因被认为可能性低而总分不高。
- 制定缓解措施:从最高风险开始,讨论团队可以采取哪些具体行动来避免或减轻该风险。
- 针对“用户采纳度低”的风险,团队决定进行持续的用户访谈和客户沟通。
- 文档化与跟进:将讨论结果整理成文档并分享。这是一个“活文档”,应在项目周期内(如每次冲刺回顾时)定期回顾和更新,并检查行动项进展。
总结与资源 📚
在本教程中,我们一起学习了两种降低失败风险的核心方法:
- 事后评估:用于在失败发生后进行结构化复盘,聚焦于系统性根本原因(而非个人追责),旨在从错误中学习并制定改进措施。
- 事前评估:用于在项目开始前主动预测风险,通过跨职能头脑风暴识别潜在问题,并提前规划缓解策略,旨在预防失败。
这两种方法共同创造了一种开放、学习型的团队文化,让人们能够安全地讨论失败和担忧。关键在于关注团队、系统和流程的改进,而不是指责个人。
拓展资源:
- 谷歌《网站可靠性工程》:书中关于事后评估和文化的章节极具启发性,即使非SRE角色也值得一读。可在线免费获取。
- PagerDuty事后评估示例:提供大量真实的事后评估文档范例,可供参考。
- 事前评估相关文章:事前评估的结构多样,建议阅读不同文章,找到适合自己团队的模式。

通过实践事前与事后评估,你和你的团队将能更从容地面对挑战,将每一次挫折都转化为迈向成功的阶梯。
026:核心技能与策略 🎯


在本教程中,我们将学习如何为 Python 技术面试做准备。我们将涵盖面试流程、需要掌握的核心技能(包括非技术技能和编程概念),并提供实用的学习资源和建议。


概述
技术面试旨在评估候选人的问题解决能力、技术知识和团队协作潜力。本教程将帮助你理解面试流程,并掌握使用 Python 成功应对面试所需的关键技能。
面试流程概览 📋


技术面试通常遵循一个标准流程。了解每个步骤可以帮助你更好地准备。
典型的流程始于招聘人员的电话筛选。这是一个初步的“脉搏检查”,招聘人员会与你进行20到30分钟的交谈,询问一些通用问题,例如你的求职动机、对公司了解以及近期的工作经历。
如果电话筛选顺利,下一步通常是技术电话面试。你会与一位工程师进行30到60分钟的交流。面试官可能会探讨你对 Python 等编程语言的理解,并根据职位要求询问一些特定领域的问题。

通过技术电话面试后,你可能会收到一个“编程作业”或“带回家评估”。这是一个小型独立项目,你需要在规定时间内(通常是2到7天)完成。这个环节旨在评估你在实际项目中的技能。

最后一步是现场面试。这通常是一个长达数小时的过程,你会与多位团队成员会面。面试内容包括技术问题(如白板编程或结对编程)和行为问题(考察你的软技能和团队协作能力)。

核心非技术技能 💡

除了编程能力,面试官也非常看重非技术技能。这些技能对于团队协作和职业发展至关重要。


上一节我们介绍了面试流程,本节中我们来看看那些至关重要的“软技能”。首先,是解决问题的能力。这不仅仅是解决一个明确定义的问题,而是指利用现有知识去应对一个可能定义模糊的具体挑战。

其次,是提出好问题的能力。一个结构清晰的问题可以极大地提高沟通效率。一个有效的提问框架是:“我试图做X,但遇到了Y。我已经尝试了A和B。你能为我指明正确的方向吗?” 这展示了你的主动性、思考过程和具体的求助需求。

最后,是理解并验证假设的能力。这包括对问题本身的假设(例如,你认为哪些是既定事实)以及对团队成员的假设。通过提问来澄清和验证这些假设,可以减少误解,促进团队顺畅运作。



核心编程概念与数据结构 🐍

扎实的编程基础是技术面试的核心。本节将介绍 Python 面试中常见的关键概念。


上一节我们探讨了非技术技能,本节中我们来看看技术面试中会考察的核心编程知识。首先,你需要熟练掌握 Python 的内置集合。它们是组织和存储数据的基本工具。

以下是 Python 中主要的集合类型:
- 列表 (List): 有序、可变的序列。
- 元组 (Tuple): 有序、不可变的序列。
- 字典 (Dict): 键值对映射。
- 集合 (Set): 无序、不重复元素的集合。

熟悉这些集合的特性和方法(如字典的 .items() 方法)能在面试中为你节省大量时间。
接下来是时间复杂度。它描述了算法运行时间随输入数据规模增长的变化趋势。在面试中,你经常需要分析自己代码的时间复杂度。

常见的几种时间复杂度及其表示法为:
- 常数时间 O(1): 运行时间不随输入大小改变。
- 对数时间 O(log n): 运行时间随输入大小呈对数增长。
- 线性时间 O(n): 运行时间与输入大小成正比。
- 平方时间 O(n²): 运行时间与输入大小的平方成正比。


递归是另一个重要概念。一个递归函数包含两个部分:基线条件(何时停止)和递归步骤(如何向基线条件推进)。其核心思想是函数调用自身来解决更小的子问题。

面向对象编程 (OOP) 在 Python 面试中也经常出现。你需要理解类、对象、实例属性与方法等概念。当被问到“为什么使用 OOP?”时,你的回答应涵盖三个核心原则:封装、继承和多态。


最后,你需要了解基础的数据结构与算法。这包括线性数据结构(如栈、队列、链表)和非线性数据结构(如树、图),以及相关的算法(如排序、搜索)。



学习资源与练习建议 📚

理论知识需要结合实践来巩固。本节将为你提供一些高效的学习路径和资源。


上一节我们介绍了核心的编程概念,本节中我们来看看如何有效地学习和练习。首先,强烈推荐一本免费的在线书籍:《Problem Solving with Algorithms and Data Structures using Python》。这本书涵盖了面试所需的大部分数据结构和算法,并提供了 Python 代码示例。
对于练习,关键在于从失败中学习。不要等到“完全准备好”才开始申请。尽可能多地参加电话面试和技术筛选,即使失败了,也要分析原因,并将经验应用到下一次尝试中。

请记住,你不需要让每一家公司都想雇佣你。你只需要找到一家合适的公司。如果某次面试没有成功,那通常意味着这家公司或这个职位在当时与你的情况不匹配,而不是对你个人能力的否定。



总结与回顾 🎉
在本教程中,我们一起学习了如何为 Python 技术面试做准备。

我们首先回顾了技术面试的典型流程,从招聘人员筛选到现场面试。接着,我们探讨了至关重要的非技术技能:解决问题、有效提问和理解假设。然后,我们深入讲解了面试中常见的 Python 核心概念,包括内置集合、时间复杂度、递归、面向对象编程以及基础的数据结构与算法。最后,我们提供了实用的学习资源和练习建议,鼓励你通过实践和反思不断进步。

技术面试的源头可以追溯到考察问题解决能力和模式识别能力。保持好奇心,坚持练习,并记住在追求技术精进的同时,也要做一个优秀的协作者。祝你面试顺利,成功获得心仪的工作机会!
027:为应用程序添加灵活性 🧩


在本教程中,我们将学习如何通过插件系统为Python应用程序添加灵活性和模块化。我们将从一个简单的命令行绘图应用开始,逐步将其重构,使其能够支持多种数据格式和图表类型。核心概念将使用代码和公式进行描述。
概述
我们将构建一个能够读取数据文件(如CSV、JSON)并绘制图表的命令行应用。最初,应用功能固定。随后,我们将通过引入插件架构,使其能够轻松扩展以支持新的数据格式和图表类型,而无需修改核心代码。

1:从简单应用开始 📝

首先,我们创建一个基础应用。它使用click库处理命令行参数,使用pandas读取CSV数据,并使用matplotlib绘制简单的折线图。
以下是初始代码:


import click
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path

@click.command()
@click.argument("file_path")
def main(file_path):
"""读取CSV文件并绘制折线图。"""
path = Path(file_path)
data = read_data(path)
plot_data(data)
def read_data(filepath):
"""读取CSV文件并返回Pandas DataFrame。"""
return pd.read_csv(filepath)
def plot_data(data):
"""绘制Pandas DataFrame的折线图并显示。"""
data.plot()
plt.show()

if __name__ == "__main__":
main()

这个应用功能完整,但缺乏灵活性。它只能处理CSV格式,并且图表类型固定。
2:识别扩展需求 🔍
上一节我们创建了一个基础应用。本节中我们来看看当需求变化时会出现什么问题。例如,如果用户有JSON格式的数据文件,当前应用将无法处理。
尝试用JSON文件运行应用会导致错误,因为read_data函数只调用了pd.read_csv。为了支持新格式,我们必须在read_data函数中添加条件判断逻辑。
def read_data(filepath):
"""根据文件后缀读取CSV或JSON文件。"""
suffix = filepath.suffix.lower()[1:] # 去掉点号,例如 ‘csv‘
if suffix == "csv":
return pd.read_csv(filepath)
elif suffix == "json":
import json
with open(filepath) as f:
data = json.load(f)
return pd.DataFrame(data)
else:
raise ValueError(f"不支持的格式: {suffix}")

这种方法在格式较少时可行,但随着支持格式的增加,read_data函数会变得臃肿且难以维护。每个新格式都需要添加新的if-elif分支。


3:引入模块化与插件概念 🧱

上一节我们看到了硬编码格式支持的局限性。本节中我们来看看如何通过模块化来解决这个问题。


核心思想是:将每种数据格式的读取逻辑分离到独立的函数中。这样,每个函数只负责一件事,符合单一职责原则。

首先,我们创建一个名为readers.py的模块:
# readers.py
import pandas as pd
import json
def read_csv(filepath):
"""读取CSV文件。"""
return pd.read_csv(filepath)
def read_json(filepath):
"""读取JSON文件。"""
with open(filepath) as f:
data = json.load(f)
return pd.DataFrame(data)
然后,在主应用中,我们需要一种方式来根据文件后缀动态调用正确的函数。一种方法是使用字典映射:
# 在主应用中
from pathlib import Path
import readers
READERS = {
"csv": readers.read_csv,
"json": readers.read_json,
}
def read_data(filepath):
suffix = filepath.suffix.lower()[1:]
reader_func = READERS.get(suffix)
if reader_func is None:
raise ValueError(f"不支持的格式: {suffix}")
return reader_func(filepath)
这比之前的if-elif链更清晰。但是,我们仍然需要手动维护READERS字典。添加新格式时,必须修改这个字典。

4:使用装饰器自动注册插件 🎀


上一节我们通过字典映射改进了结构,但注册过程仍是手动的。本节中我们来看看如何使用装饰器实现插件的自动注册。
装饰器是Python的一个强大功能,它是一个接收函数作为参数并返回函数的函数。我们可以创建一个装饰器,用它来将函数注册到我们的插件系统中。
以下是实现自动注册的步骤:
- 创建一个全局字典来存储插件。
- 创建一个装饰器函数,将被装饰的函数注册到字典中。
- 使用这个装饰器来标记我们的读取器函数。
更新后的readers.py模块:
# readers.py
import pandas as pd
import json
# 存储所有已注册的读取器插件
_REGISTRY = {}
def register(func):
"""装饰器:将函数注册为插件。"""
# 使用函数名作为插件键(例如 ‘read_csv‘)
_REGISTRY[func.__name__] = func
return func # 装饰器返回原函数,不改变其行为


@register
def read_csv(filepath):
return pd.read_csv(filepath)


@register
def read_json(filepath):
with open(filepath) as f:
data = json.load(f)
return pd.DataFrame(data)


def get_reader(plugin_name):
"""根据名称获取已注册的读取器函数。"""
reader_func = _REGISTRY.get(plugin_name)
if reader_func is None:
raise ValueError(f"插件 ‘{plugin_name}‘ 未找到。")
return reader_func
现在,主应用可以这样调用:

# 在主应用中
from pathlib import Path
import readers

def read_data(filepath):
suffix = filepath.suffix.lower()[1:]
# 构建插件函数名,例如 ‘read_csv‘
plugin_name = f"read_{suffix}"
reader_func = readers.get_reader(plugin_name)
return reader_func(filepath)
这样,添加一个新的读取器(如read_excel)只需要在readers.py中定义新函数并用@register装饰即可。主应用代码无需任何修改。


5:推广模式与使用PiPlugs 📦

上一节我们为数据读取器创建了一个插件系统。本节中我们来看看如何将相同的模式应用到图表绘制器(plotters)上,并介绍一个现成的工具来简化这个过程。

我们可以创建另一个模块plotters.py,用同样的@register装饰器模式来管理不同的图表类型(如折线图、散点图)。

然而,手动管理多个插件模块(readers, plotters, writers等)会带来重复的样板代码。为了解决这个问题,可以使用一个名为 PiPlugs 的第三方包。
PiPlugs 提供了一个基于装饰器的轻量级插件架构。它帮助我们:
- 自动发现插件。
- 将插件组织在独立的包/目录中。
- 提供统一的调用接口。
以下是使用PiPlugs重构后,插件目录结构的示例:
my_plotting_app/
├── main.py
└── plugins/
├── readers/
│ ├── __init__.py
│ ├── csv.py
│ └── json.py
└── plotters/
├── __init__.py
├── line.py
└── scatter.py

在每个插件文件中,使用PiPlugs的@register装饰器:
# plugins/readers/csv.py
import pandas as pd
import piplugs
@piplugs.register
def read_csv(filepath):
"""PiPlugs插件:读取CSV。"""
return pd.read_csv(filepath)

在主应用中,可以这样调用:
# main.py
import click
from pathlib import Path
import piplugs


@click.command()
@click.argument("file_path")
def main(file_path):
path = Path(file_path)
suffix = path.suffix.lower()[1:]
# 使用PiPlugs调用插件
data = piplugs.call(f"readers.read_{suffix}", filepath=path)
piplugs.call("plotters.line", data=data)

if __name__ == "__main__":
main()
PiPlugs 在后台处理了插件的导入、注册和调用,让我们能更专注于业务逻辑。
6:插件系统的应用场景与最佳实践 💡
上一节我们介绍了使用PiPlugs来管理插件。本节中我们来看看插件系统的典型应用场景和一些需要注意的地方。
插件架构在以下场景中非常有用:
- 数据读写器:支持多种文件格式(CSV, JSON, Excel, Parquet)。
- 计算引擎:在不同后端(如NumPy, TensorFlow, PyTorch)之间切换。
- 通知器:将消息发送到不同平台(Email, Slack, SMS)。
- GUI组件:动态加载不同的界面部件。
使用插件系统时,请遵循以下最佳实践:
- 定义清晰的接口:确保同一类别的所有插件(如所有
readers)具有相同或兼容的函数签名(参数和返回值)。例如,所有数据读取器都应接收filepath参数并返回DataFrame。 - 处理参数差异:如果不同插件需要不同的参数,调用方需要知晓并传递正确的参数。PiPlugs允许传递任意
**kwargs,但调用者需负责匹配。 - 错误处理:始终对插件查找失败的情况进行优雅处理。
- 文档化:为你的插件系统编写文档,说明如何添加新插件。
总结
在本教程中,我们一起学习了如何为Python应用程序构建一个灵活的插件系统。
- 我们从一个简单的、功能固定的绘图应用开始。
- 我们识别出添加新功能(如数据格式)时代码会变得臃肿的问题。
- 我们通过模块化和字典映射将不同功能解耦。
- 我们利用装饰器实现了插件的自动注册,大大提升了可扩展性。
- 我们介绍了PiPlugs包,它提供了生产就绪的插件基础设施,帮助我们管理更复杂的插件生态。
- 最后,我们探讨了插件系统的应用场景和最佳实践。

通过插件架构,你可以创建出易于维护、测试和扩展的应用程序,并允许其他开发者甚至用户为其添加功能,从而极大地提升了软件的灵活性和生命力。
028:远程工作真的有效吗?





在本节课中,我们将学习远程工作的有效性、相关研究数据以及如何成为一名成功的远程工作者。我们将探讨远程工作的利弊,并提供实用的建议。

概述


我们将从个人经历和研究数据出发,分析远程工作的实际效果。接着,我们会讨论如何说服雇主支持远程工作。最后,我们将提供五个关键步骤,帮助你成为一名高效的远程员工。


个人经历:从挣扎到成功


上一节我们介绍了课程主题,本节中我们来看看演讲者劳伦·谢弗的个人远程工作经历。
劳伦最初因配偶工作地点问题而需要远程工作。她在办公室工作一年后开始远程办公,但前六个月非常挣扎。她经历了生活环境的巨大变化,并且在工作初期参与的团队中,她是少数远程成员,存在时区沟通和任务边缘化的问题,这让她感到孤立和缺乏动力。
经过六个月的调整,她找到了成功的方法。她主动更换团队以学习新技能,并开始在会议上发言。她甚至领导创建了一个拥有超过11,000名成员的居家办公网络小组,获得了宝贵的领导经验。自2010年以来,她一直成功地进行远程工作,职业生涯持续发展。
远程工作的动机


上一节我们了解了远程工作可以成功,本节中我们来看看人们选择远程工作的常见原因。


员工选择远程工作的动机多种多样。以下是几个主要原因:
- 家庭与生活平衡:因配偶工作、子女教育或照顾家人而无法搬迁。
- 避免通勤:节省通勤时间,用于家庭、爱好或休息。
- 专注的工作环境:摆脱开放式办公室的干扰。
- 地理自由:实现边旅行边工作的生活方式。

对于雇主而言,提供远程工作选项也有好处:
- 扩大人才库:吸引全球顶尖人才。
- 提升士气与留存率:提高员工满意度和忠诚度。
- 节省成本:减少办公空间和搬迁费用。
- 提高生产力:减少病假和干扰,员工更专注。

研究数据怎么说?
上一节我们探讨了远程工作的动机,本节中我们来看看支持远程工作的研究数据。
多项研究证实了远程工作的积极效果。
2019年 Stack Overflow 开发者调查显示:
- 约40%的开发者希望有远程工作选项。
- 全职远程开发者平均拥有多出约60%的专业编码经验。
- 开发者认为生产力最大的挑战是分心的工作环境和通勤时间。

2014年《经济学季刊》针对携程呼叫中心的研究(对照实验)发现:
- 居家员工绩效提升 13%(9%源于工作时间更长,4%源于每分钟处理更多电话)。
- 工作满意度更高,员工流失率降低 50%。
- 一个值得注意的缺点是,居家员工的晋升率降低了约50%,原因包括“眼不见为净”和缺乏发展人际技能的机会。
- 当允许员工自主选择工作地点后,整体绩效提升了 22%。

其他研究支持:
- 思科2009年调查发现,远程工作提升了生产力、工作满意度和灵活性,并为公司节省了大量成本,同时减少了温室气体排放。
- 一项涵盖20年46项研究的文献综述指出,远程工作对员工自主权、工作满意度、绩效有积极影响,并且未发现其对职业前景有直接的负面影响。
当然,研究也指出了远程工作的潜在挑战:
- 专业孤立可能影响表现。
- 远程经理可能带来负面影响。
- 存在职业停滞、孤立、干扰及工作家庭界限模糊的风险。
- 远程员工可能倾向于过度工作,导致倦怠。


如何说服你的老板?

上一节我们看到了支持远程工作的数据,本节中我们来看看如何向雇主提出远程工作的请求。
如果你希望尝试远程工作,可以采取以下策略:
- 提议一个实验:明确实验周期(如三个月)、工作安排(如每周两天在家)和评估标准。
- 解决合作疑虑:使用在线工具(如Slack)模拟“茶水间对话”;建议定期举行线下创新头脑风暴会议,然后远程执行。
- 强调公司利益:谈论节省成本、吸引全球人才、降低员工流失率以及提升生产力。
- 保持积极态度:以合作而非威胁的方式沟通。

成为成功远程员工的五个关键
上一节我们讨论了如何争取机会,本节中我们来看看确保远程工作成功的关键实践。
以下是五个帮助你成为高效远程员工的核心建议:
第五,加入合适的团队。
尽可能选择完全分布式的团队,以确保沟通的公平性和透明度。了解团队的沟通风格(如是否常用视频会议),并主动与每位新同事安排简短的一对一交流,分享个人近况以建立信任。
第四,保持高效。
设定清晰的每日目标,专注于最重要的两到三件事。打造一个你喜欢且符合人体工学的专属工作空间,避免在沙发等非正式场所工作。
第三,积极沟通。
在团队常用的沟通工具上保持“在线”状态并及时响应。不仅要讨论工作,也要进行真诚的私人交流。注意个人形象管理,积极地为自己的工作和成就“打广告”。

第二,安排线下见面。
尽可能创造机会与同事面对面交流,无论是公司会议、客户拜访还是共同参加行业活动。线下建立的信任能使线上协作更加顺畅。




第一,主动防止倦怠。
这是最重要的建议。坚持午休,在会议间隙稍作伸展,并在工作日结束时坚决关闭电脑和工作通知,明确区分工作与生活界限,避免过度劳累。


总结


本节课中我们一起学习了远程工作的有效性。研究表明,远程工作对员工(提升满意度)、公司(提高生产力、降低成本)和环境(减少通勤排放)都有益处。虽然它并非适合所有人,并且存在如孤立和过度工作等挑战,但通过选择合适的团队、保持高效、积极沟通、安排线下见面以及主动防止倦怠,远程工作完全可以非常成功。关键在于主动争取自己所需的工作方式,并为实现高效远程协作付出努力。
029:放下深度学习 - 何时不该使用神经网络

概述
在本教程中,我们将学习如何根据实际项目需求选择合适的机器学习模型。深度学习虽然强大,但并非适用于所有场景。我们将探讨回归、基于树的模型和基于距离的模型等替代方案,并分析它们各自的优缺点、适用条件以及实现成本。通过本教程,你将能够为你的问题选择最合适、最高效的解决方案。
深度学习的现状与挑战
深度学习是一套强大的技术,近年来取得了令人瞩目的成就,例如在游戏(如Dota 2、围棋)和机器人操作等领域的突破。然而,构建深度学习模型的过程通常是缓慢、乏味、费力且昂贵的。模型的有效性、参数选择和架构设计往往依赖于大量的猜测和测试,缺乏坚实的理论指导。此外,训练大型模型(如GPT-2、BigGAN)的计算成本可能高达数万美元甚至更多。
因此,在决定使用深度学习之前,需要仔细评估项目条件。
何时考虑使用深度学习?
在以下情况下,可以考虑使用深度学习:
- 任务复杂度:任务本身是复杂的,如果一个人完成该任务需要超过一秒钟的思考时间。
- 错误容忍度高:能够接受模型产生一些奇怪或难以解释的错误。神经网络的工作原理与人类认知不同。
- 无需模型解释:项目不要求对模型的决策过程进行解释或审计。例如,在金融风控等需要高度可解释性的领域,神经网络目前并非最佳选择。
- 拥有海量标注数据:通常每个类别需要超过5000个标注样本。如果使用迁移学习,数据量要求可以适当降低。
- 拥有充足的时间和资金:具备足够的资源用于模型训练、超参数调优、数据标注和计算开销。
如果以上条件不能全部满足,那么我们应该考虑其他机器学习方法。
深度学习替代方案(全):回归模型
上一节我们介绍了深度学习的适用场景,本节中我们来看看第一种经典且强大的替代方案:回归模型。
回归模型要求我们手动为数据关系选择一个函数族(例如线性关系)。与神经网络不同,我们对何种回归模型适用于何种问题有更原则性的理解。
以下是回归模型的主要特点:
-
优点:
- 训练速度快:尤其是在使用优化良好的库时。
- 小数据集表现好:在数千个数据点上也能获得有意义的结果。
- 易于解释:可以清晰地理解每个特征对结果的影响。
-
缺点:
- 需要更多数据准备:例如,需要处理特征间的多重共线性问题。
- 需要模型验证:必须检查数据是否满足模型的基本假设(如误差分布),否则结果可能无效。
一个强大的回归示例:混合效应回归
混合效应回归能很好地处理分组数据中的复杂模式,例如“辛普森悖论”——在整体数据中呈现一种趋势,但在各子组内呈现相反趋势的现象。
以下是使用Python statsmodels 库实现混合效应回归的示例代码:
import statsmodels.api as sm
import statsmodels.formula.api as smf
# 假设 df 是一个包含 ‘admit‘(是否录取), ‘gre‘, ‘toefl‘, ‘university_rating‘ 的 DataFrame
model = smf.mixedlm(“admit ~ gre + toefl“, df, groups=df[“university_rating“])
result = model.fit()
print(result.summary())
通过查看模型输出的系数,我们可以做出可操作的建议。例如,如果TOEFL分数的系数比GRE分数更高,那么建议学生优先提高TOEFL成绩。
小结:回归建模需要更多手动干预和验证,但它成本低、计算需求小、能处理较小数据集,并且具有无与伦比的可解释性。
深度学习替代方案(二):基于树的模型
回归模型在可解释性上优势明显,但有时我们需要更“自动化”的、对复杂关系捕捉能力更强的工具。本节我们来看看基于树的模型,特别是集成方法。
决策树通过递归地根据特征值划分数据来做出决策。在实践中,单独使用决策树容易过拟合,因此更常用的是集成方法,如随机森林。
以下是基于树的模型的主要特点:

-
优点:
- 所需数据预处理少:可以处理数值和类别型特征,无需大量转换。
- 用户友好:有大量优秀且易用的库(如XGBoost, LightGBM)。
- 性能强大:在众多数据科学竞赛中表现出色。
-
缺点:
- 容易过拟合:需要通过剪枝、设置树深度等参数来控制。
- 可解释性低于回归:虽然可以得到特征重要性排序,但难以进行“如果…那么…”的精确推理。
- 计算成本高于回归:集成多棵树需要更多的训练时间。
以下是使用 XGBoost 库的简单示例:
import xgboost as xgb
from sklearn.model_selection import train_test_split

# 准备数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 创建并训练模型
model = xgb.XGBRegressor() # 对于回归任务
model.fit(X_train, y_train)
# 进行预测
predictions = model.predict(X_test)
小结:基于树的方法在分类问题上通常表现优异且易于上手,但需要更多计算资源和数据来防止过拟合,其可解释性介于回归和深度学习之间。
深度学习替代方案(三):基于距离的模型
当项目时间非常紧迫,或者数据量极少时,我们需要更轻量级的解决方案。本节介绍基于距离的模型,例如支持向量机(SVM)和K近邻(KNN)。
这类模型的核心思想是:在特征空间中,距离越近的样本越可能属于同一类别或具有相似的值。
以下是基于距离的模型的主要特点:
-
优点:
- 极其轻量快速:训练和预测速度通常很快。
- 适用于极小数据集:例如SVM只需少量样本即可工作。
- 良好的初步探索工具:可以快速验证某个问题是否具有可预测性。
-
缺点:
- 整体精度通常较低:在拥有足够数据时,性能往往不如树模型或深度学习。
- 可解释性一般:虽然比深度学习好,但不如回归模型清晰。
以下是使用 scikit-learn 实现支持向量回归(SVR)的示例:
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
# 数据标准化(对SVM很重要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 创建并训练模型
model = SVR(kernel=‘rbf‘)
model.fit(X_train_scaled, y_train)
# 进行预测
predictions = model.predict(X_test_scaled)
小结:基于距离的模型是快速原型设计和处理小数据集的利器,所需时间、金钱和数据都最少,但性能天花板也相对较低。
如何选择模型?总结与建议
本节课中我们一起学习了四种主要的机器学习模型类型。选择哪一种,取决于你的具体约束条件和目标。
以下是决策参考框架:
- 资源维度:评估你拥有的时间、资金和数据量。
- 如果三者都极其充裕,可以优先尝试深度学习以追求极致性能。
- 如果资源有限,依次考虑基于树的模型、回归模型或基于距离的模型。


- 性能与特性维度:
- 最强大、最灵活:深度学习。
- 最易解释:回归模型。适合需要因果推断或回答“如果…会怎样”的场景。
- 最用户友好:基于树的模型。特别适合分类任务的入门和实践。
- 最轻量快速:基于距离的模型。适合快速验证或资源极度受限的环境。

核心结论:数据科学不等于深度学习。优秀的从业者应像拥有一个完整的工具箱,根据问题的具体情况(钉子的大小、材质)选择合适的工具(锤子、螺丝刀、扳手),而不是盲目使用最炫酷的那一把。

避免成为“手里只有锤子,看什么都像钉子”的人。理解并掌握多种模型,将使你能够更高效、更可靠地解决现实世界中的问题。
030:欢迎与会议指南

在本教程中,我们将学习 PyCon 2019 开幕主旨演讲的核心内容。演讲者首先介绍了会议的基本信息、行为准则和各项设施,随后深入探讨了 Python 社区面临的挑战与未来机遇。我们将内容整理为清晰的教程,帮助你理解演讲的要点。
会议欢迎与行为准则
早上好。欢迎来到 2019 年 PyCon。本次会议在俄亥俄州克利夫兰举行。我代表 PyCon 2019 工作人员,感谢所有志愿者、演讲者、赞助商和与会者。
PyCon 致力于为每个人提供安全的环境和积极的体验。这与我们之间的任何差异无关。为确保这一点,所有工作人员、与会者、演讲者、赞助商和志愿者都必须遵守我们的行为准则。


请务必熟悉行为准则。你可以在指定 URL 上找到该准则及其链接的事件处理程序,或访问 us.pycon.org 网站,在“关于行为准则”部分查看。
如果在会议期间,你担心有人可能违反了行为准则,鼓励你进行举报。即使你不确定事件是否违反准则,或发生场所是否在准则范围内,也鼓励你提交报告。组织者欢迎所有报告,即使不采取行动,也能帮助创造更安全的空间。

你可以通过电子邮件 pyconusreport@pycon.org 提交报告,或通过短信/电话联系 12018628572。

在会议期间,你可以找到穿着颜色鲜艳 T 恤的事件响应者。
屏幕上是联系信息。鼓励你拍照、记录或以其他方式保存在手机中。
会议设施与日程安排
如果你是第一次参加 PyCon,我们努力详细说明会议的每一个部分。如果你错过了昨天的新人介绍会,相关视频将在指定 URL 发布,你可以观看 Adrian 分享会议的每一个细节。
你现在位于会议中心。它相当大,我们将逐步介绍。
首先,我们提供了一些无障碍措施。今年所有演讲都将提供实时字幕。在每个演讲房间的中央保留了无障碍通道的空间。会议符合 ADA 标准。如果你有任何顾虑或需要额外便利,请告诉工作人员。
你现在位于展览大厅 A。在接下来的三天中,这里将举行全体会议,包括主题演讲和闪电演讲。闪电演讲的时间是今天下午 5:40,以及周六和周日早上。考虑腾出时间参加闪电演讲。如果你有兴趣进行闪电演讲,可以在注册处附近找到报名表。

你左边是博览会大厅,位于展览大厅 B 和 C。所有餐饮将在这里进行,直到周日。你可以在屏幕上的 URL 找到餐饮菜单和成分信息。如果你在注册时表示需要特殊餐饮,请查看大厅中的标志。
博览会大厅也是所有赞助商展位所在地。鼓励你拜访赞助商展位,了解他们使用 Python 的情况以及他们支持社区的原因。赞助商使得 PyCon 成为可能,并帮助我们控制注册费用。他们的到来代表了对我们社区和本次会议的巨大投资。

提醒一下,赞助商展位仅在今天和明天开放。到周日,整个空间将重新配置。
除了赞助商,创业区也位于博览会大厅。今天和明天会有一些新公司展示与 Python 相关的新鲜且有趣的事情。请注意,这些展示会有所变化,因此今天在那里的人员明天可能不在。如果有机会,你可以去创业区两次。
到周日,整个房间将重新配置为我们的海报和招聘会。
屏幕上是我们的分组讨论室。请注意,这些房间(H 室的宴会厅)距离主会场有一些距离。如果你打算在那里参加讲座,请考虑时间安排。
今年我们有五条讲座轨道和 95 场会议。大多数讲座会被录制,并在会议后在线提供。所以当你面临两个想看的讲座冲突时,听从你的直觉。
如果你参加一个讲座,时间还剩下,演讲者允许提问时,请记住以问题的形式表达你的问题。
我们有很多房间可供开放空间使用,今天、周六和周日全天都有。这些房间分配给任何人自组织一小时的聚会风格活动,并将开放从早上 8 点到晚上 11 点。这是一个很好的机会,可以深入讨论某个主题,或者找到有共同兴趣的人。
鼓励你考虑通过举办自己的开放空间来塑造会议。你可以去公告板发布自己的开放空间或查看现有内容。今年新推出的功能是,你可以访问屏幕上的网址,查看当天公告板的照片。
这些房间(25C 和 26ABC)是 PyCon 孵化器计划的所在地。PyCon 是一场通过志愿者的指导逐年增长和发展的会议。去年我们推出了孵化器计划,旨在为任何人提供机会,在 PyCon 上实现他们的想法。
我们很高兴今年能迎来首届孵化器计划,并有三个新概念加入。今天全天,你可以参加 Lust PyCon,这是一天的西班牙语演讲活动,将在 25C 房间进行。欢迎并鼓励你参加单场演讲或整天活动。
今年的新活动是 PyCon 艺术展。这是一个小型艺术节,将集中于叙事、表演和视觉艺术。今晚 8 点到 10 点,你可以在 26ABC 房间找到该活动。
PyCon 孵化器维护者峰会也是今年的新活动,旨在为项目和维护者建立一个实践社区。这将是闪电演讲和讨论的混合,时间是从上午 10 点到下午 2 点在 25C 房间进行。
今年的新活动还有指导冲刺,明天从下午 2:30 到 6:30 在 25C 房间进行。这是新手参与开源项目的介绍,在有经验的人指导下进行。
这些活动专注于为经验较少的开发者提供支持环境,让他们学习、合作,扩展网络并庆祝他们的胜利。这些是今年的 PyCon 孵化器计划。如果你有一个想法,PyCon 孵化器计划很可能会继续,请留意 CFP。
PyCon 在接下来的几天内几乎占用了整个会议中心。在此期间,所有公共空间将被 PyCon 相关的活动覆盖。没有规定说你必须参加任何特定的活动。考虑花些时间加入一张桌子或一个小组,结识新朋友。
一个改善走廊活动和 PyCon 整体体验的建议来自 PSF 董事会成员 Eric Holscher。这是“吃豆人规则”。在一群人中站着时,总是留出空间让一个人加入你的团队。这个建议有助于为某人加入你的团队创造一个身体上欢迎的空间。
在整个空间中,我们有三个性别中立的卫生间。一个在 17 号和 18 号房间附近,一个在 25 A、B 和 C 号房间附近,另一个在博览会大厅内。会议中心还有两个母亲室,分别是 17 号和 18 号房间。房间的访问说明贴在外面。
如果你需要帮助或需要进入那里,请查看并获取说明。如果你发现自己需要休息,专注于一些工作或只是深呼吸,我们在 24 号房间有一个安静的房间。请在使用时尊重“安静”这个词。
一步走出门外,克利夫兰总是美丽的。场馆附近是普拉提拍卖活动,定于 5 月 4 日星期六晚上 6:30 在希望宴会厅举行。去年筹集了 30,000 美元用于普拉提活动。5 美元的入场费是一个好交易。
即使你不来竞标,这也可能是我们这里最有趣的一个晚上。就在这里拐角处,如果你沿着湖边走,左转进入第九街,再走到码头,你会发现摇滚与流行音乐名人堂和大湖科学中心。这些地方是 PyCon 晚宴的主办地,一个在今晚 6:30,另一个在会议结束后的周日 6:30。
这两个晚间活动都是需要票的活动。当你在登记时签到时,你应该拿到小票。你需要把那些票带到这些活动上。如果你拿到了票但不打算参加,请将你的票转给其他人。这有助于减少食品浪费。
如果你希望随身携带这些信息,有印刷的程序。还有 pycon.org 网站。今年我们又有一本指南。这是一个特定的应用程序,里面包含了所有的日程信息和房间号码等。你可以在 pycon.us/guidebook 找到它。

事务提醒与日程变动

负责本次会议财政责任的 Python 软件基金会目前正在进行筹款活动。如果你能够,请考虑访问屏幕上的这个网址,它会带你到一个页面,上面有关于那个筹款活动的所有信息。

如果你注册了 PyCon 乐跑和步行活动,那将在周日 7 点开始。巴士将于 6 点开始运行。但是大概会在 6 点 30 分左右到。明天早上有一个不同的活动,不是我们的。不过请注意,如果你是开车来会议中心,这会造成一些拥堵。从明天早上 7 点到 11 点,计划多留一点时间到达这里。

赠品包可以领取。里面你会找到一个小设备。感谢 DigiKey 和 Adafruit,所有与会者将获得一个可以运行 Python 的 Circuit Playground Express。因此对此有两个注意事项。第一,如果你要经过某些需要安检的地方,请提前将这个从包里拿出来,并告诉安检员它是什么。第二个建议是考虑周日把这个带到我们的闭幕主题演讲。
你需要领取的是你的 T 恤。由于剪裁和重量,与去年相同,我们选择的颜色似乎没有那么多颜料。这些 T 恤非常薄。因此在公共场合穿之前,确保你试穿一下。如果你在取 T 恤后对其有疑虑,请告诉我们。
最后,有一些日程变动。我之前提到的移动指南应用程序和标识已经更新。你的打印程序没有更新。所以请花点时间检查博览会大厅前的公告板,以查看替代信息。
但总的来说,在周六,大宴会厅 B 发生了替换。由 Philip James 讲授的《大象与蛇:使用 Mastodon 与 Python》替代了由 Nina 讲授的《Pythonista 的代码审查技能》。通过 Chris Wilcox 发布您的第一个 Python 包并自动化未来的发布,替代了使用 Python 的代码阅读器。
今年我们有专业摄影师在现场。如果你不想被偷拍,你可以去注册处领取一个徽章夹。把你的徽章换进去,把挂绳放上,摄影师会避免拍到你。在后期制作中,我们会确保不使用任何佩戴有红色横幅徽章的人的照片。
最后一点,关于社交媒体。如果你想在会议上发推文,可以这样吸引工作人员或组织者的注意,推特 @PyCon。如果你想发布有关会议的内容,用标签 #pycon2019 发推文。
主题演讲:Russell Keith-Magee 博士

现在,我非常兴奋地介绍我们的第一位主题演讲者,Russell Keith-Magee 博士。他是 BeeWare 项目的创始人,长期以来一直是社区成员,该项目将 Python 带入移动设备。他也是 Django 的长期贡献者。

非常感谢,Ernest。你好,克利夫兰。我是 Russell Keith-Magee。今天能在这里我感到非常荣幸。我在 Python 社区活跃并显眼地参与了超过 13 年。我出生并至今生活在珀斯,西澳大利亚。
珀斯有着迷人的历史,对世界的影响超出许多人所意识到的。有些故事是便于比喻的,所以今天我想向大家讲述珀斯的故事。也许我们可以在这个过程中学到一些关于生活的宝贵教训。
首先,一些术语。来自西澳大利亚的人叫沙地虫。沙地虫有一个关于如何开始大型公共活动的传统。为了承认土著人民的历史,从 1970 年代开始,澳大利亚的公共聚会逐渐开始承认聚会发生的土地并不总是白人的土地。这被称为“土地承认”。
因此,我想承认我来自 Wajuk Nung Aabuja。我想承认 Erie Honan 和 Haudenosaunee 人民,今天我们聚会所在土地的传统拥有者。承认他们与土地、水域和文化的持续联系,并向他们的过去、现在和未来的长者表示敬意。
此外,作为内容警告。我将在本次演讲的后面讨论抑郁和自残的问题。如果这对你有影响,请做你需要做的自我照顾。我会在进入这一部分之前再给出一个警告。
在我的日常工作中,我为 Savada 做数据工程。Savada 是一家市场研究公司。他们使用 Python 和数据科学来帮助品牌了解他们的客户。Savada 让我有灵活性,可以在全球各地游走。但有了像 Savada 这样出色公司的支持,真是太好了。
我通过在 Django 上的工作参与了 Python 社区,并为人所知。我早在 2006 年就加入了 Django 核心团队。由于我住在珀斯,直到 2008 年我才见到 Django 核心团队的另一位成员。Django 是 Python 生态系统的重要组成部分,但并不是唯一的部分。
在我目前的日常工作中,我并不使用 Django。但是我广泛使用 NumPy、Jupyter 和 Pandas 及其相关工具生态系统。此外,Python 还有许多其他用途。它被用作操作系统自动化的脚本语言,并作为教语言的 DevOps 控制语言。
这些都不是一夜之间发生的。Python 作为一种语言已经 28 岁了。大约花了 10 年时间 Python 才在我们行业获得任何显著的关注,再过 10 年才获得广泛支持。因此,我们得到了这个:3 个聚会。有 500 人从世界各地组织前来,汇聚到克利夫兰,讨论一种编程语言,为期几天。
值得花点时间反思 28 年前 Guido 所说的意义,以及我们作为一个社区自那时以来所做的努力。这应该得到庆祝,因为这不是一项小成就。并且因为这是 Python 社区的大舞台,我们在这里有真正的机会展望未来。
在这个周末以及下周的冲刺中,我们有机会畅想伟大的创意。我们需要解决哪些问题,集体关注哪些方向,接下来的几周、几个月和几年。所以,为了启动这个对话,我想向在座的每个人提出一个问题:你们认为 10 年后的 Python 会是怎样的?
我唯一确定的是,在座的每个人对这个问题的回答都会不同。当一个语言被这么多人用于如此多不同的事情时,这种情况是不可避免的。但如果你今天在这里,可能可以合理地假设你对 Python 有一定的喜爱。而在 10 年后,你可能希望看到 Python 至少保持如今的活力。
我相信在座的没有人希望重写 PyPI 上的所有库,或重建 Python 生态系统中的所有社区和用户组,或在新社区中重新建立我们的生态系统规范,仅仅因为 Python 不再是新项目的可行语言。因此,这个问题:“你怎么看待 10 年后的 Python?”实际上是在询问我们今天需要做些什么,才能确保 Python 仍然是一个充满活力、相关且健康的社区。
黑天鹅事件与 Python 的未来
现在是时候分享一些更有趣的事实了。西澳大利亚州的州鸟是黑天鹅。它出现在西澳大利亚州的旗帜上以及珀斯市的徽章上。穿过珀斯中部的河流被称为天鹅河。但是,黑天鹅也是一个隐喻。在 17 世纪之前,所有天鹅都是白色的这一点是众所周知的。其他颜色的天鹅被认为不存在。
然后在 1697 年,荷兰探险家威廉·德·弗拉明意外地访问了西澳大利亚,并发现天鹅也可以是黑色的。这彻底改变了人们对天鹅的认识。在 2001 年,SAS 和统计学家纳西·M·尼古拉斯·塔勒布将此作为更广泛的隐喻。
塔勒布将黑天鹅事件定义为对观察者来说是意外的事件,具有重大影响。但事后来看,这可以很容易地解释。回想起来,很明显天鹅可以是黑色的。但在有人访问西澳大利亚并看到了它之前,没有人想到要挑战这一假设。
黑天鹅事件不一定是即时发生的。其影响可以随着时间逐渐显现。个人计算机的崛起就是一个黑天鹅事件。1947 年,IBM 的首席执行官托马斯·沃森曾著名地说,全球市场可能只需要五台计算机。如今,几乎在座的每个人都携带着五台计算机。回顾往昔,沃森的评论听起来只是天真的。但在当时,这并不具争议性。
在我看来,当我们思考 Python 的未来时,我们需要考虑 Python 的黑天鹅事件。Python 现在是一种流行的语言。我们希望它保持受欢迎。那么有什么可能影响这种受欢迎程度的因素呢?行业内、硬件上、社区中可能发生什么变化,会影响 Python 的受欢迎程度?
黑天鹅事件在事后看来才显而易见。但避免这些事件的最佳方法是积极挑战你的假设。而问题是:“如果这些假设被证明是错误的,会发生什么?”我认为我们可能已经让人们对此感到担忧。
你在哪里运行 Python?在 Python 存在的几乎整个时间里,一台计算机是一个坐在你桌子上的大盒子。在过去的 10 年里,我们看到了全新类别计算设备的出现,它们体积更小,通常是便携式的。手机、平板、手表、机顶盒。这些设备正变得无处不在。它们正在取代笔记本电脑,成为主要的计算设备。
如果你访问 Python.org,平板电脑、手机、机顶盒、手表甚至没有提及。对我来说,这似乎是对过去 10 年日常计算体验变化的相当大忽视。手机和平板电脑的市场渗透率是桌面和笔记本电脑从未见过的。然而作为一个社区,我们并没有一个故事来讲述如何在这些设备上使用 Python。
如果笔记本电脑不存在,会发生什么?有些小众设备。在我的平板电脑上没有 Python 的安装程序。我为什么要学习 Python?
并非所有的 Python 都在笔记本电脑上运行。你在服务器上安装了 Python。你确保用户没有启用 JavaScript。如今,祝好运,找一个在没有启用 JavaScript 的情况下正常工作的网站。与移动设备一样,过去 10 年,代码执行的位置发生了显著变化。越来越多的代码在浏览器中运行。
JavaScript 已经从可选的客户端附加组件演变为在浏览器中实现关键业务逻辑的语言,甚至成为取代服务器上 Python 的语言。再说,如果我是一个初学者程序员,面对 JavaScript,这是一种我可以用来在浏览器中获得原生体验的语言,并且可以将其过渡到服务器上。另一方面,有 Python,这是一种仅在服务器上工作的语言。我为什么要学习 Python?
就算面对这两个问题,我仍然坚持学习 Python,并且我达到需要安装一个包的地步。我已经使用 Python 20 年,在这段时间里,我认为没有曾经有一段时间,我会将 Python 的包装故事描述为稳定的。这是因为信息在互联网上永远存在。过时的建议在被认为是正确答案后仍会持续存在,而出于好意的错误信息也在 Stack Overflow 上传播,从而加剧了这个问题。
这种情况糟糕到足以成为漫画的笑话。XKCD 1987,兰德尔·门罗讽刺的 Python 环境体验已经下降到使他的笔记本电脑变成有毒废物的地步。布雷特·卡农对此漫画进行了非常出色的撰写,试图诊断布兰登电脑上发生了什么。这是一次非常有启发性的尸检,值得阅读。
但布雷特指出的一件事,从我寻找黑天鹅的角度来看,这一点让我最担忧。为什么这个笑话可以被提出?好笑话总是包含真理的元素,你对此真理的反应很重要。这个笑话是关于 Python 是什么的叙述的一部分。如果这个图景不是积极的,从长远来看,那就是一种生存威胁。
而且我甚至还没有涉及到如何将我的应用程序分发给其他人的问题。无论你是在分发 Django 应用程序还是用户空间应用程序,Python 在如何将我的代码交给他人方面一直没有一个一致的故事,尤其是当那个他人不是开发者而只是想使用我的应用程序时。与之形成对比的是,例如,PHP,其发布故事是使用 FTP 上传所有文件,或是 go 故事就是在这里,提供这个可执行文件。

现在,我并不是说 PHP 或 go 解决方案是万灵药,它们并不是。我也不是说 Python 的故事能像那些一样简单。但我们完全没有故事。而我们正在与那些有故事的语言竞争。而且,我为什么要学习 Python,如果我无法清楚地回答如何将我的代码交给非程序员朋友的这个基本问题呢?
现在,我刚刚识别出四个可能的黑天鹅。也许问题会是别的什么。我知道在这个房间里有些人会声称 Python 的 C API 是一个潜在的问题,或者是 Gill,或者缺乏好的前端故事,或是将重点放在 Unix 和 Mac 上而忽视 Windows。也许问题根本不在技术上。也许它将是文化上的。

Python 社区或开发过程中的某些方面可能会驱逐某些用户群体。或潜在贡献者因为感知到的敌意、冲突或利益不合而退缩。也许根本不会是这些问题。关键是我们无法预测未来,但我们可以挑战我们的假设。我们可以批判性地审视自己作为一个社区。我们可以检查行业和社会的趋势,并根据这些评估制定计划。

BeeWare 项目与创新
这些生存问题是我几年前改变开源贡献焦点的原因之一。正如我之前所说,我在 Django 上建立了自己的声誉。但现在,我将大部分志愿时间用于 BeeWare 项目。
对于那些之前未接触过的人,BeeWare 是一个用于在 Python 中创建本地用户界面的开源工具和库的集合。它适用于桌面,也适用于 iOS、Android、单页网页应用和其他新硬件平台。这个工作的一个关键部分是让 Python 在手机和平板上运行。
如何在 iOS 的浏览器上运行 Python 代码,连接到本地 API 和那些平台。将这些 API 封装在一个跨平台层中。另一个重要部分是开发分发方案,搞清楚如何与本地平台工具集成,以将用 Python 编写的应用程序封装成带安装程序的应用,或上传到应用商店或 Play 商店。
BeeWare 仍在开发中。目前的状态是一个引人注目的概念验证。它不是一个可以开始用来开发关键任务应用程序的第一个版本产品。但我已经能够证明,用 Python 编写跨平台的本地应用程序是可行的。我并不是唯一一个在解决这个问题的人,例如 Kivy 项目具有相似的目标,但在其基础方法上有所不同。
就 BeeWare 而言,大约 18 个月前,我在 2017 年的 PyCon Australia 上进行了一个现场演示,我编写了一个华氏度转摄氏度的应用程序,大约 50 行 Python 代码。然后将其部署在 MacOS 上作为 MacOS 应用,在 Linux 上作为 Linux 应用,在 Windows 上作为 Windows 应用,在 iOS 上作为 iOS 应用,在 Android 上作为 Android 应用,以及作为单页网页应用,所有这些我在 17 分钟内完成了。
当我说部署时,我并不是说我在 Mac 上运行了代码。你所拥有的是一个独立的 .app 文件。在 Windows 上,是一个 MSI 安装程序。在 iOS 和 Android 上,是一个打包的应用程序,运行在设备模拟器上,但可以轻松上传到应用商店。而且它是在浏览器中运行,完全在客户端,包括在浏览器中进行华氏度到摄氏度的转换。
现在,正如你可能想象的,这是一项大工程。我已经在 BeeWare 上工作了几年,有些时候我确实感觉这就像是在推一块巨石上山。不幸的是,自从我进行那个演示以来,显著的进展并不多。甚至还有一些回退。目前 Android 和网页支持由于某些原因出现了问题。这是因为这不是我的日常工作。我正在利用我称之为“闲暇时间”的时间来进行尝试。因此,进展很慢。
美洲杯的启示
但尽管如此,我对 Python 的未来实际上感到乐观。接下来我会解释原因。我需要告诉你关于 Sangerpah 历史的另一部分,关于美洲杯比赛。我需要告诉你关于美洲杯的事情。美洲杯是世界上最古老的国际体育奖杯。美洲杯比赛大约每四年举行一次。
你们来自旧金山的人可能还记得 2013 年在旧金山湾举行的美洲杯比赛。从 1851 年第一场比赛到 1980 年,获胜者是纽约游艇俱乐部。在 1983 年,纽约输给了皇家珀斯游艇俱乐部,游艇澳大利亚也打破了 132 年的获胜纪录。我无法夸大这一事件在澳大利亚的重要性,尤其是在珀斯。这是将珀斯首次推向世界舞台的事件。
今天的许多人可能在不知道原因的情况下也知道 1983 年美洲杯的事。有些人可能听过我提到“我来自南半球”这句歌词。这是澳大利亚摇滚乐队 Men at Work 的一首歌中的一句。这首歌也是澳大利亚的非官方主题曲。它在美国流行主要是因为 1983 年美洲杯挑战。
电影《鳄鱼邓迪》的构思和融资部分得益于澳大利亚在美洲杯中获得的曝光。需要明确的是,Outback Steakhouse 与此无关。
除了珀斯的联系,美洲杯和澳大利亚为什么重要呢?首先,让我们谈谈纽约的获胜纪录。1983 年比赛中的游艇被称为 12 米游艇。尽管名字如此,实际上与船的长度无关。它描述了一套复杂的游艇设计规则,包括帆面积、水线长度等等。
然而,美洲杯并不总是在 12 米级游艇上进行比赛。在 1956 年之前,使用了不同类别的游艇。根据奖杯的契约,卫冕俱乐部必须接受常规挑战。但他们可以设定这些挑战的规则。因此多年来一直如此。纽约游艇俱乐部会施加规则,比如“挑战者必须来自海洋游艇俱乐部,并必须独自航行到比赛现场。”
也就是说,来自英格兰的挑战者必须跨越大西洋,而卫冕者只需从纽波特港出发。令人惊讶的是,在这些条件下,纽约的游艇通常更轻、更快。所以,关于美洲杯的第一课是,仅仅遵循规则是不够的。你必须确保竞争是公平的。纽约游艇俱乐部并没有违反任何规则。他们并不是为奖杯制定契约的人。他们只是打了手中牌。
但在这样的情况下,没人会认为竞争是公平的。你有选择。你可以继续赢得对你有利的竞争,或者你可以改变你能控制的事情,使其成为公平的竞争。但你不能声称你是在凭借实力赢得的世界最佳,除非你先平衡了竞争环境。这是否可以作为 IT 行业的隐喻?你知道,我想这可能是。
但是值得称赞的是,纽约游艇俱乐部在 1958 年采用了 12 米规则。因此,从那时起,竞争一直是公平的。那么为什么没有其他人赢呢?好吧,一只黑天鹅出现了。帆船是一项竞争非常激烈的活动,而纽约游艇俱乐部总是提供强有力的防守,配备设计良好的游艇和高效能的船员。
当你加入主场优势,了解纽波特港的当地海域和盛行风时,继续获胜就更容易了。而 12 米游艇的设计本质上是在对一个成熟设计进行微调。船只之间存在一些变体,但它们都是对基本设计的相对成熟的调整。
澳大利亚二号是一只黑天鹅。当澳大利亚二号抵达美国时,船的下半部分笼罩在秘密之中。这引发了各种疯狂的理论,尤其是当它开始赢得比赛时。在两次不同的情况下,澳大利亚团队抓住了潜水员试图偷看。各种指控称,无论那条覆盖物下有什么,它肯定是非法的。
但是,这支敏锐的团队一直将秘密保持到胜利庆祝会上。当球队老板站在码头上,呼叫将船抬到空中让大家观看时,揭示了带翅膀的龙骨。在大多数船只上,龙骨是一个简单的平面,用于降低船体的重心,以抵消帆力。澳大利亚二号的设计师本·莱辛对此有不同的看法。
他认为龙骨是一个水动力表面,所以我们就像对待一个水动力表面一样来处理它。他在龙骨的底部添加了一对小翼。这些小翼的作用类似于喷气式飞机机翼上的小翼。飞机上的小翼使机翼产生更多升力和更少阻力。龙骨上也是如此。小翼使龙骨在保持船只直立方面更有效,这意味着帆也更有效,因此船速更快。
蓝色涂料是伪装,因此如果你真的拍到船在水中的照片,你会看到传统龙骨形状的轮廓。快进到 1987 年。澳大利亚防御队。每艘游艇都有一些秘密。它们是玻璃纤维船体,各种龙骨形状,动态龙骨。这样的创新持续到今天,碳纤维船体、坚固的帆和更多。
美洲杯现在不再在 12 米游艇上进行。2021 年的下一届美洲杯将以所谓的 AC 75 游艇进行比赛,这些游艇可以说是飞行的船只。它们轻得惊人,帆产生的推力如此之大,以至于能够使用它们的龙骨作为一个水上飞行者,从水中升起。这些船以惊人的速度航行。
所有这些创新都是因为一个团队决定挑战 12 米游艇设计的假设。这是有争议的,风险很大,但它奏效了。自此以后,游艇比赛再也不是同样的样子。为了强调这个隐喻,澳大利亚 2 号的救生艇被命名为黑天鹅。
WebAssembly (Wasm) 的机遇
这与 Python 有什么关系呢?好吧,我认为我们已经看到了为我们行业所展示的风翼龙,那就是 Wasm。对于那些尚未接触到它的人,Wasm 是 WebAssembly。它的起源是一个研究项目,旨在确定一组可以在现代 JavaScript 引擎中快速执行的原始 JavaScript 操作。
这组原始操作非常接近汇编语言的能力,分配内存、简单的整数浮点运算等等。因此,您可以使用这组原始操作并将 C 代码编译为这个汇编语言目标。Wasm 是这项工作的扩展,定义了一种用于传输汇编内容的二进制格式,而不是将 JavaScript 作为要被解析和解释的内容进行传输。
这使得 Wasm 代码更小,更快处理,但其可移植性并不比 JavaScript 差。因为归根结底,它就是 JavaScript。实际上,它将 JavaScript 分成了两个部分。一个快速的沙箱运行时,几乎在所有浏览器中都可用,以及一种针对该运行时的语言。
对于我们行业的目的,这意味着规则已经改变。您不再需要采用 JavaScript 语言来采用 JavaScript 运行时。这意味着 Python 和其他语言有机会成为可用于浏览器客户端逻辑的语言。
Wasm 仍处于相对早期的阶段。仍然有一些问题正在解决,其中一个大问题是 DOM 与 Wasm 之间的交互。而制作一个可行的基于 Wasm 的 Python 将需要一些工作。不过,在这个领域已经进行了一些实验,但风翼龙已经被揭示出来。我们如何利用这些知识则取决于我们。
我们是否将深入技术,发挥创造力,确定如何利用这些规则为我们所用,并驶向一个飞行的船只?还是我们将继续在平底 12 米游艇上比赛,并且想知道为什么我们不再赢得比赛?
团队合作与资源投入
但我们还可以从澳大利亚学习更多。Ben Lexen 是一位非常有才华的游艇设计师。当他在澳大利亚 2 号的龙骨底部添加翼尖时,他做了一件了不起的事情。但他并不是一名竞技帆船手。John Bertrand 是一位出色的船长,却无法设计游艇。他和船上的其他 14 个人以及在救生艇上和港口的整个支持团队一起,展示那 15 名水手的竞争能力。
帆船是一项团队运动。你不仅需要顶尖的工程师或船长。你需要广泛的技能。只有当这些技能结合在一起,形成一个运转良好的团队时,你才能取得成功。软件的情况也是如此。尽管有关于伟大的软件开发是由独自在地下室敲打代码的黑客完成的历史叙述,这几乎从来不是事实。
我们今天所看到的 Python 社区是无数小时努力的结果,来自无数人。而且由于主要是软件工程师,代码往往获得最多的关注。没错,编写优秀软件需要大量的努力,做到这一点需要时间和技能。但这仅考虑代码。任何项目,尤其是像 Python 这样的大型项目,远不止代码。
项目和团队管理是一项技能。图形设计和用户界面管理是一项技能。开发者关系是一项技能。技术写作是一种技能。好吧,这些技能可以广泛认为是全面软件工程师的职责。但那些甚至与软件毫不相关的事情呢?谁来组织社区活动?谁提供法律建议?谁负责沟通和公共关系?
谁与潜在捐赠者建立关系并管理筹款?谁与谷歌、微软和亚马逊等公司的战略交易?确保 Python 在产品路线图上得到支持。事实证明,世界上有些人无法编程,但他们有非常有用的卓越技能。
澳大利亚在这里也有另一个教训。本·莱克斯顿和他的团队并不是出于良心而做所有这些工程工作。竞技帆船被形容为在冷水淋浴中撕掉百元钞票的能力。澳大利亚 2 挑战由一位名叫艾伦·邦德的绅士资助。1993 年的挑战是邦德资助的第三次挑战,总共吸收了数亿资金。

如果没有邦德的资金,澳大利亚 2 挑战是不可能实现的。如果没有对多个杯赛的承诺,设计团队就无法深入了解所有游艇设计。资助研究工作,最终也不会取得有翼龙骨的成果。无论是竞技帆船还是软件,都需要专业知识。但专家们通常期望得到报酬。
在开放源代码软件开发的泡沫内部,很容易忽视一些文化规范在其他行业并不正常的事实。开放源代码软件是一个行业,显然期待你在完成 9 到 5 的日常工作后,回家继续做一些免费的事情,并鼓励一个数十亿美元的组织从你的努力中获益。
在大多数行业,如果你擅长某件事,你就会获得报酬。而软件以外的人也有非常有用的技能,这些技能是许多开源项目急需的。但写软件也是如此。人们为编写软件获得良好的报酬,但有很多软件是应该被编写的,但并不是。或者不是及时完成,因为我们在等待志愿者来完成这项工作。
Django 是一个网络框架,DSF 协调其网站最近的 redesign 花了超过三年。为什么?因为如果你需要志愿者,你立即限制了任何人可以投入到工作的时间和精力。我不想贬低志愿者对 Python 和 Python 社区所做出的重大贡献。但我确实想挑战这样的观念:志愿服务是 Python 或开源进步的唯一途径。
如果你为人们的时间付费,他们更有可能保持对问题的关注,直到设计完善或后果被考虑,而工作就完成了。我们在 Python 社区看到这个令人惊叹的例子,PyPI 的重写。PyPI 已经存在了 15 年,几乎一半的时间都急需重写。每个人都同意需要重写,但工作从未完成。为什么?因为没有人被支付去做这项工作。
然后 Mozilla 给了 PSF 一笔 170,000 美元的拨款,工作在六个月内完成。为什么?因为几个人能够专注于完成任务,而不是在周末的孩子足球比赛之间抽时间修复 bug,或者试图说服他们的老板,“哦,改善 PyPI 在下一个季度不会为我们带来任何收益。这对社区来说是值得的活动。”
PyPI 是一个不幸的稀有例子。我们还留下了哪些机会,或者我们是否面临着因为没有为其提供资源而无法把机会转化为现实的风险?我们对自身增长施加了哪些限制?是因为没有人热心地自愿花时间来解决某个空白或利用某个机会吗?

我们排除了哪些人参与开发过程,因为他们有其他生活承诺或无法合理解释将所有空闲时间投入到志愿者工作中?或者因为他们不能接受短期的拨款合同,因为他们住在美国并需要医疗保险?
这不仅仅是关于维护和现有基础设施。历史证明,研究与开发是确保长期成功的方法,而不进行研发的团队最终会被淘汰。澳大利亚 II 对研发的投资导致了风鳍的出现,并推动了整整一代游艇设计的进步。
我之前识别的黑天鹅问题不是简单修复就能解决的。这将需要集中努力,可能在过程中会遇到许多死胡同。我在利用 BeeWare 方面尽力而为,但实际上我只在周末做这些。大部分 Python 及其生态系统也是如此。我们今天拥有的 Python 是在志愿者的闲暇时间开发的,或者是工程师从雇主那里挤出的时间片段。
如果不必如此呢?如果 Python 拥有一个研发部门,一个专注于 Python 生态系统战略任务的常驻工程团队呢?当贝尔电话公司给予一群工程师战略性工程的资源时,我们得到了 Unix。当施乐在帕洛阿尔托资助一个团队进行战略性工程时,我们得到了施乐园,这为我们提供了图形用户界面、以太网和激光打印机。
当你给予有才华的人思考大问题的资源时,惊人的事情会发生。Python 社区有很多有才华的人。我们只需给予他们思考大问题的资源,去做大事情,而不必证明工作将在下个季度产生利润,或者不必将一半的时间花在为下个资助撰写提案上。这也可能为在开源开发中历史上代表性不足的群体提供高调的有薪职业机会。
然而,我们面临的根本问题是,我们如何为此支付费用?开源是一种出色的工程方式,但它不是商业模式。我们需要找到如何在不妥协社会目标的情况下为开源工程目标提供资金的方法。然而,这个问题属于学术经济研究领域。
埃莉诺·奥斯特罗姆因对共同池资源的首次研究而获得 2009 年诺贝尔经济学奖。共同池资源的现实世界例子包括森林或牧场。这些是任何人都可以访问的资源。最佳个人策略是尽可能多地从资源中获取,但最佳共同策略是合作,限制你的获取,为了维护资源以最大化其长期健康和生产力,必须回馈。
奥斯特罗姆的研究考察了现实世界中管理的共同池资源示例。其中一些资源已经集体运营和维护了数百年。奥斯特罗姆研究的关键发现之一是可持续共同池资源所需的条件。而其中一个条件是排除。为了维持共同池资源,你必须能够限制对资源的访问,仅限于那些致力于遵守社区原则的人。
问题在于这与自由软件指南直接对立,后者指出不能限制人们重新分发软件的能力。我不知道我们如何调和这两种立场。这可能需要对我们长期以来认为神圣的一些观点进行非常严肃的考虑,或者至少要明确区分软件和其周围社区。但我们无法避免这些讨论。这种差异的后果对我们社区中的人们产生了深远的影响。
心理健康与社区支持
为此,我有最后一段关于桑格拉帕历史的分享,作为预告,我们即将进入我之前提到的敏感话题。这位绅士是查尔斯·耶尔维丁·奥康纳,CYO·康纳。他出生于 1843 年,1891 年成为西澳大利亚公共工程部的工程主管。他负责的领域很广泛,但他因两个重大公共工程而被人们铭记。
第一个是弗里曼特尔港。实际上,珀斯是一个不适合大城市的地方。没错,离一切都很远,但海上的城市需要港口。珀斯没有天然的深水港,所以他们建造了一个。他们通过疏浚斯旺河的河口来实现这一点。这一切是在 1890 年代的 CYO·康纳的指导和监督下完成的。
但这并不是 O'Connor 唯一的成就。他的另一个成就是更大的“金矿水方案”。1890 年代初,库尔加迪和卡尔古利镇附近发现了黄金。这引发了西澳大利亚的淘金热。你可以从地图中注意到这些城镇。他们处在沙漠的中央。那里的自然水源稀缺,但人们需要水。因此,他们愿意支付每加仑五先令的费用,将水从珀斯用马车运送过来。
经过通货膨胀调整,这相当于每加仑 100 美元。黄金如此丰厚,人们还是愿意支付。1890 年代的金矿热潮使西澳大利亚的人口在四年内翻了一番。但人们仍然死于首次疾病,所以政府想采取行动。CYO·康纳的解决方案是修建一条 530 公里(330 英里)的管道,从珀斯山的蒙达灵水库到卡尔古利,沿途设有八个泵站以将水送到目的地。

将水提升 340 米的高度,水滴从蒙达灵到卡尔古利需要五天时间。而且,这一切是在 1890 年代建成的。弗里曼特尔港和金矿管道在马车和蒸汽机时代构思、规划并交付。这两个项目都是巨大的 undertaking。它们的实现需要当时政府的相当财务投入。
但和所有雄心勃勃的计划一样,它们并不总是受欢迎。对这两个项目的批评非常普遍。那些被拒绝的替代方案的提出者纷纷通过媒体表达他们的不满。珀斯《周日时报》的两位连续主编将追责 O'Connor 作为他们的个人使命,被追究他浪费公共资金的方式。
他们在社论中抓住每一个机会指责他腐败、无能,并呼吁对他的活动进行皇家委员会调查。所有这些公众的批评和压力都对 O'Connor 产生了影响。在 1902 年一个清晨,他选择了结束自己的生命。
这两个项目,弗里梅特港和金矿管道,它们雄心勃勃,但并不是轻率的。它们设计精良,工程
031:是否使用GIL - 多核CPython的未来 🐍

在本节课中,我们将学习CPython解释器中一个核心但颇具争议的组件——全局解释器锁(GIL)。我们将探讨它的工作原理、带来的影响,以及社区为突破其限制、实现真正的多核并行所进行的努力,特别是通过子解释器这一途径。
CPython执行模型概览 🏗️
为了理解GIL,我们首先需要了解CPython是如何执行代码的。CPython的运行时结构可以看作是多层嵌套的。
一个进程内只有一个运行时。运行时中可以包含一个或多个解释器。每个解释器内可以运行一个或多个Python线程,每个线程都维护着自己的调用栈。而调用栈中的每一帧,都有一个执行字节码的eval循环。
以下是这个模型的简化表示:
进程 (Process)
├── 运行时 (Runtime)
├── 解释器 (Interpreter) #1
│ ├── Python线程 (Thread) #1
│ │ └── 调用栈 (Call Stack)
│ │ └── 帧 (Frame) -> eval循环
│ └── Python线程 (Thread) #2
│ └── ...
└── 解释器 (Interpreter) #2
└── ...


当一个Python程序启动时,源代码会被编译成包含字节码指令的代码对象。eval循环的工作就是逐条执行这些指令,推动程序运行。在单线程场景下,指令的执行是确定且线性的。
多线程与竞争条件 ⚔️
上一节我们介绍了单线程下的线性执行。本节中我们来看看当引入多个线程时会发生什么。
如果程序创建了第二个线程,那么进程中就会有两个eval循环同时运行。在多核CPU上,这两个循环可以真正地并行执行,而不仅仅是交替运行(并发)。这带来了一个关键问题:执行顺序变得非确定。我们无法再将所有指令扁平化为一个单一的、确定的列表。

这种非确定性是竞争条件的根源。当一个线程正在基于某个数据的当前状态进行操作时,另一个线程可能修改了该数据,导致第一个线程的假设失效,从而引发错误。
以下是一个竞争条件的简单示例:
import threading
spam = 42 # 共享数据


def thread_a():
global spam
if spam == 42: # 假设spam是42
# 在此瞬间,线程B可能已经修改了spam
spam = ‘hello‘ # 此操作可能破坏线程B的假设


def thread_b():
global spam
if spam == 42: # 这个假设可能因为线程A而失效
print(‘spam is 42‘) # 这里可能打印,也可能不打印,行为不确定

为了解决竞争条件,我们使用锁(或互斥锁)来同步线程。锁确保同一时间只有一个线程能进入被保护的代码段(临界区),从而为这些代码段的执行提供确定性顺序。
全局解释器锁(GIL)的引入与作用 🔒
既然我们知道了锁可以保护共享资源,那么GIL的作用就很好理解了。GIL是一个保护CPython运行时内部状态免受竞争条件影响的锁。
如果没有GIL,CPython解释器本身的许多内部数据结构(如内存分配器、垃圾回收器的引用计数等)在多线程环境下将是不安全的。GIL确保了在解释器级别,一次只有一个线程能够执行Python字节码。
在之前的执行模型图中,当存在多个线程时,GIL的存在意味着虽然有两个eval循环,但它们不能同时执行字节码。解释器会在执行一定数量的字节码指令后,强制线程释放并重新争夺GIL,这使得线程看起来是交替运行的。
GIL也会在某些阻塞型I/O操作(如文件读写、网络请求)期间被释放,允许其他线程在等待I/O时运行,这在一定程度上缓解了其对I/O密集型任务的影响。
GIL的利弊与现有应对方案 ⚖️
GIL的设计带来了一些代价,但也并非全无好处。
GIL的主要代价包括:
- 限制多核并行:无法充分利用多核CPU进行并行计算,这对CPU密集型任务影响显著。
- 引发性能焦虑:即使实际影响不大,它的存在本身就会引起开发者的担忧。
GIL带来的好处包括:
- 简化CPython实现:使解释器核心更简单、更易于维护。
- 提升单线程性能:无需为所有数据结构添加细粒度锁,减少了锁开销。
- 简化C扩展开发:C扩展作者在操作Python对象时,通常无需担心底层的线程安全问题。
社区已经发展出多种方式来应对GIL的限制:
- 使用多进程:通过
multiprocessing模块创建多个进程,每个进程有独立的Python解释器和内存空间,从而绕过GIL。缺点是进程间通信开销和资源占用更大。 - 使用异步编程:通过
asyncio等库在单线程内处理高并发I/O任务,避免创建线程。 - 利用C扩展释放GIL:在执行不涉及Python对象的纯C/C++计算时,C扩展可以临时释放GIL,允许其他线程运行。
- 使用无GIL的Python实现:如Jython(基于JVM)和IronPython(基于.NET CLR),它们依赖底层虚拟机的内存管理,自身没有GIL。
此外,历史上也有直接尝试移除GIL的项目,如“Gilectomy”。虽然面临单线程性能下降等挑战,但这些探索为未来提供了宝贵经验。
子解释器:隔离与并发的未来路径 🚀
上一节我们讨论了现有的绕开GIL的方案。本节中我们来看看一个更具革命性、旨在从CPython内部解决问题的方案——子解释器。
子解释器并非新概念,它已在CPython的C API中存在多年。其核心思想是:在一个进程内创建多个相互隔离的Python解释器实例。每个子解释器拥有自己独立的命名空间和状态,类似于一个轻量级的进程。
以下是引入子解释器后的模型变化:
进程 (Process)
├── 运行时 (Runtime)
├── 主解释器 (Main Interpreter)
│ └── (线程和状态...)
├── 子解释器 (Subinterpreter) #1 # 拥有独立状态
│ └── (线程和状态...)
└── 子解释器 (Subinterpreter) #2 # 拥有独立状态
└── (线程和状态...)
目前,这些子解释器仍然共享同一个GIL,因此尚未实现真正的并行。但关键在于它们提供了状态隔离。PEP 554提案旨在将子解释器功能通过标准库(如interpreters模块)暴露给普通Python用户。

子解释器通过通道进行通信,这是一种进程间通信的简化模型。数据通过通道在解释器间传递,实现了“选择性共享”,而非传统多线程的“完全共享”,这更安全,也更符合某些并发模型(如通信顺序进程,CSP)。
迈向无GIL的未来:每解释器GIL(PIG) 🎯
子解释器为我们描绘了一个美好的蓝图,但实现真正的多核并行,关键一步在于将GIL从进程级别移至解释器级别,即每个子解释器拥有自己的锁(可以戏称为“PIG”,Per-Interpreter GIL)。
这样,运行在不同子解释器中的线程就可以被操作系统调度到不同的CPU核心上真正并行执行,因为它们受不同的锁保护。运行在同一解释器内的线程则仍受该解释器GIL的约束,交替运行,这与当前行为一致。


实现这一目标的主要挑战包括:
- 最小化全局状态:需要将目前由全局GIL保护的运行时状态减少到最低限度,并将剩余状态用更细粒度的锁保护。
- 改造C扩展模块:许多现有的C扩展模块假设存在唯一的全局GIL,需要使其适应“每解释器GIL”的新模型。这是最大的兼容性挑战。
这项工作正在由核心开发者(如本演讲者Eric Snow)积极推动。虽然困难重重,但许多相关工作(如重构CPython内部API、改善C扩展生态)即使最终不用于移除GIL,也对提升CPython的整体健壮性和性能大有裨益。
总结与展望 🌈
本节课中我们一起学习了CPython中GIL的来龙去脉。我们从CPython的执行模型出发,理解了多线程下的竞争条件为何需要GIL这样的全局锁来保护解释器内部状态。我们分析了GIL的优缺点以及当前社区如何应对其限制。
更重要的是,我们探讨了通过子解释器实现状态隔离,并最终迈向每解释器GIL的未来路径。这条路径有望在保持向后兼容性的前提下,为CPython带来真正的多核并行能力。

Python的未来是并行的。虽然道路曲折,但通过子解释器、每解释器GIL等持续的努力,CPython正在稳步向着更高效利用现代多核计算资源的方向演进。对于开发者而言,关注这些进展,并在设计并发程序时合理选择多进程、异步IO或未来的子解释器模型,将是构建高性能Python应用的关键。
032:Python 在数据提取和分析中的应用 📊

在本节课中,我们将学习如何利用 Python 进行数据提取与分析,这是记者在数据驱动新闻时代的重要技能。我们将从基础概念入手,逐步介绍关键工具和步骤,帮助你理解如何从原始数据中挖掘故事。

数据提取与分析概述 🔍
数据提取与分析是数据新闻的核心环节。它涉及从各种来源获取数据,并通过处理、清洗和分析,将其转化为有意义的见解和故事。

上一节我们介绍了课程的整体框架,本节中我们来看看数据提取与分析的具体流程。

数据提取 🧲

数据提取是指从网页、数据库、API接口或文档中获取原始数据的过程。对于记者而言,常见的数据来源包括政府公开数据、社交媒体和新闻报道。
以下是进行数据提取时常用的几种方法:
- 网络爬虫:使用 Python 库(如
requests和BeautifulSoup)自动抓取网页内容。 - API 接口:通过调用网站提供的应用程序编程接口获取结构化数据。
- 公开数据集:直接从政府或研究机构的数据门户下载数据文件。

数据清洗与整理 🧹
获取的原始数据通常包含错误、缺失值或不一致的格式,无法直接用于分析。数据清洗的目的是将“脏数据”转化为干净、可用的数据集。

上一节我们了解了如何获取数据,本节中我们来看看如何处理这些原始数据。
数据清洗的关键步骤包括:

- 处理缺失值:识别并填充或删除数据中的空值。
- 格式标准化:确保日期、数字和文本等数据格式统一。
- 删除重复项:移除数据集中的重复记录。
一个简单的数据清洗代码示例如下,使用 pandas 库处理缺失值:

import pandas as pd

# 读取数据
df = pd.read_csv('raw_data.csv')
# 填充缺失值
df.fillna(method='ffill', inplace=True)
# 删除重复行
df.drop_duplicates(inplace=True)
数据分析 📈
数据清洗完成后,就可以进行分析以发现模式、趋势和异常值。分析结果可以为新闻报道提供事实依据和深度视角。

以下是几种基本的数据分析方法:

- 描述性统计:计算数据的平均值、中位数、标准差等,了解数据的基本分布。
- 数据可视化:通过图表(如折线图、柱状图、散点图)直观展示数据关系和趋势。
- 相关性分析:探索不同变量之间是否存在关联,其强度可以用相关系数
r表示,公式为:
r = Σ[(xi - x̄)(yi - ȳ)] / √[Σ(xi - x̄)² Σ(yi - ȳ)²]
从分析到故事 📖
数据分析的最终目的是支撑新闻报道。记者需要将数据洞察转化为通俗易懂的语言和引人入胜的故事。
将数据转化为故事需要遵循以下要点:

- 突出关键发现:从分析结果中提炼出最核心、最具新闻价值的一点。
- 提供上下文:解释数据背后的含义,说明为什么这个发现重要。
- 使用人性化案例:用具体的个人或事例来诠释数据,使故事更生动。


总结 🎯
本节课中我们一起学习了数据提取与分析的完整流程。我们从数据提取开始,探讨了如何获取原始数据;接着深入数据清洗,学习了如何整理数据以供分析;然后介绍了基本的数据分析方法;最后,我们探讨了如何将数据分析的结果转化为有力的新闻报道。掌握这些技能,你将能更有效地利用数据讲述真相。
033:从传感器数据到泄漏修复的完整流程 🛰️➡️💻


在本教程中,我们将跟随马修·戈登在 PyCon 2019 的演讲,学习如何利用 Python 技术栈构建一个端到端的系统,用于探测和定位甲烷气体泄漏,从而为应对气候变化做出实际贡献。我们将涵盖从数据采集、云端处理、机器学习分类到生成可操作报告的全过程。

概述:气候变化与甲烷问题 🌍
气候变化是一个严峻的全球性问题。按照当前轨迹,到2050年全球气温可能上升2到2.5摄氏度,导致大规模人口迁移。造成这一问题的主要原因中,约57%来自化石燃料燃烧产生的二氧化碳,另有约14%-15%来自甲烷。甲烷是天然气的主要成分,其温室效应是二氧化碳的60倍。它是一种廉价、清洁的燃料,但由于水力压裂技术的普及,泄漏问题严重。甲烷本身无色无味,难以探测,因此开发有效的监测技术至关重要。
上一节我们介绍了问题的背景,本节中我们来看看解决这一问题的技术方案是如何设计和实现的。

硬件平台:机载泄漏调查仪 ✈️
为了解决大规模甲烷泄漏探测的难题,我们开发了名为“泄漏调查仪”的机载设备。该设备的核心是一个安装在塞斯纳飞机底部的传感器舱。
其核心组件包括:
- 光谱仪:用于检测大气中甲烷的特定吸收波长。
- 6轴GPS:精确记录飞机的位置(经度、纬度、高度) 和姿态(滚转、俯仰、偏航)。
- 光学相机:提供视觉背景,确认观测目标。
飞机以“割草机”模式飞行,扫描地面区域。传感器数据结合位置和姿态信息,通过专有算法可以生成显示地面甲烷浓度的地图。然而,硬件只是起点,真正的挑战在于数据的处理与转化。
云端数据处理:可扩展的突发科学计算 ☁️

数据采集后,首要任务是将大量数据(约100GB)从德克萨斯州的机场上传到云端进行处理。我们构建了一个基于AWS的云编排系统。
整个流程如下:
- 原始数据上传至 Amazon S3 存储。
- S3事件触发一个 Amazon SQS 消息。
- 云编排系统接收消息,启动一批高性能EC2服务器集群。
- 服务器处理数据,并将结果输出回S3。
- 处理后的数据被推送到 PostGIS(空间数据库)并供分析应用程序使用。
- 处理完成后,所有服务器被终止,以控制成本。
这种“突发计算”模式将服务器视为可随时创建和销毁的“牛”,而非需要精心维护的“宠物”,实现了高度的可扩展性和成本效益。
为了实现流程自动化,我们最初构建了一个名为 Pantsabout 的作业调度器(MVP)。它基于Ansible,并具有以下优点:
- 简单的REST API和SQS接口。
- 集成的端到端测试套件,能自动测试整个管道。
- 失败作业监控与自动终止,避免资源浪费。
然而,Pantsabout也存在缺点,主要是滥用Ansible进行控制流管理导致逻辑复杂,且缺乏作业依赖关系的全局视图。这引出了“软件Jank生命周期”的概念:软件在达到“最大Jank”峰值(用户抱怨最多)后,经过修复会趋于稳定,但随着新功能请求,开发者会意识到最初架构的缺陷,最终到达一个重构与重写成本相等的“Jank拉格朗日点”。
在达到这个点后,我们开发了第二代系统 Rube。它基于 Apache Airflow,用Python编写可单元测试的操作符,保留了端到端测试,并提供了优秀的作业调度、重试和日志查看界面。这大大降低了管道维护的负担。
数据分割:从飞行路径到独立多边形 🧩
飞机飞行时,传感器持续采集数据。我们需要将连续的扫描数据分割成有意义的独立片段(多边形),以便后续分析。这本质上是一个分类问题:区分有效的直线扫描“通过”和无效的“转弯”数据。
我们首先使用 Scikit-learn 库中的 K-Means聚类算法 进行无监督学习。选择的特征变量包括:
- 偏航角的时间导数(
d(yaw)/dt) - 速度的时间导数(
d(velocity)/dt) - 飞机的滚转角(
roll) - 飞机的俯仰角(
pitch)
# 示例:使用Scikit-learn进行K-Means聚类
from sklearn.cluster import KMeans
# features 是一个包含上述变量的数组
kmeans = KMeans(n_clusters=2) # 假设分为两类:通过和转弯
labels = kmeans.fit_predict(features)
首次运行就达到了约90%的准确率。我们将结果可视化在一个单页Web应用中,供工程师手动检查和修正错误标签(例如,转弯时传感器指向地平线产生的异常数据)。经过约一年的人工修正,我们积累了一个高质量的标注数据集。

随后,我们改用监督学习算法——Scikit-learn的多层感知器(MLP)神经网络。关键步骤是对输入特征进行正确的归一化和白化处理。


from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
mlp = MLPClassifier(hidden_layer_sizes=(100,), max_iter=500)
mlp.fit(X_train_scaled, y_train)
神经网络能够自动学习复杂模式,最终实现了“设置后即忘”的高精度自动分类,不再需要人工干预。
报告生成:从数据到可操作的指令 📄
处理数据的最终目的是指导现场人员修复泄漏。我们意识到,对于驾驶皮卡车的现场工程师来说,一个复杂的数据门户并非最佳选择;他们更需要简洁明了的行动指令。
因此,我们构建了一个报告生成系统,其核心设计是 “抽象数据提供者(Abstract Data Provider)” 模式。该模式基于 SQLAlchemy ORM 和 Jinja2模板引擎。
流程如下:
- SQLAlchemy 执行定义好的查询,从PostGIS数据库获取数据。
- 数据通过“munch”方法转换为适合模板的格式(如字典列表)。
- 转换后的数据与部分元数据一起被写回数据库,用于追踪报告状态。
- 数据被送入 Jinja2 HTML模板。
- 渲染后的HTML通过 DocRaptor 服务(支持分页)转换为PDF报告。
该系统的优势在于其可组合性和一致性。例如,同一个用于查询泄漏点的SQLAlchemy查询对象,可以被不同的“数据提供者”复用,一个用于生成泄漏清单表格,另一个用于生成地图图像。这确保了所有输出报告中的数据都是自我一致的。
我们为简单的报告创建了“默认数据提供者”,允许将SQL查询和参数直接写入HTML模板的元数据注释中,极大提升了开发效率。
成效与总结 🎯
通过上述Python技术栈构建的系统,我们能够高效地处理甲烷泄漏探测数据。在我们的第一次大规模调查中,历时三个月,所减少的甲烷排放量相当于抵消了2000个人一年的二氧化碳排放当量。目前,我们正在进行规模扩大十倍的新调查。
本节课中我们一起学习了:
- 问题背景:甲烷泄漏对气候变化的重大影响。
- 硬件基础:机载光谱仪与GPS如何采集原始数据。
- 云端架构:如何利用AWS服务构建可扩展的突发计算管道,并迭代作业调度系统(从Pantsabout到Rube)。
- 机器学习应用:如何使用Scikit-learn(从K-Means到MLP神经网络)对飞行数据进行自动分类和分割。
- 软件工程实践:如何设计可维护、可复用的报告生成系统,将数据转化为现场人员可执行的指令。


整个流程展示了Python生态中的强大工具(如Airflow, Scikit-learn, SQLAlchemy, Jinja2)如何整合起来,解决一个具有重大现实意义的复杂环境问题。从天空中的传感器到地面维修人员手中的PDF报告,Python在每一个环节都发挥着核心作用。
034:编写更简单且易于维护的 Python


概述
在本教程中,我们将学习如何衡量和简化 Python 代码的复杂性。我们将探讨代码复杂性的概念、如何量化它,并介绍一个名为 wily 的工具来帮助我们跟踪和管理代码的复杂性。最终目标是编写更易于理解和维护的代码。
第1章:为什么简单的代码很重要? 🎯
代码不仅是写给计算机的指令,也是写给其他开发者的。Python 的设计初衷就是易于人类阅读。清晰的代码结构、有意义的命名和简洁的逻辑,都是为了更好地传达你的意图。
简单的代码更容易维护和修改。当代码难以理解时,修改它更容易引入错误。此外,简单的代码也更容易进行充分的测试,这是保证软件质量的关键。
第2章:如何衡量代码复杂性? 📏
上一章我们讨论了简单代码的重要性,本章中我们来看看如何衡量代码是否复杂。
代码行数:一个粗糙的指标
最直观的衡量方式是计算代码行数。理论上,行数越少越好。
# 示例:一个简单的函数
def calculate_sum(a, b):
result = a + b
return result

然而,单纯追求行数少可能导致代码可读性变差。例如,将多行代码压缩成一行虽然减少了行数,但牺牲了清晰度。
# 不推荐:虽然行数少,但难以阅读
def calc(a,b):return a+b
正如 Tim Peters 在《Python之禅》中所说:“可读性很重要”。因此,我们需要更科学的度量方法。
圈复杂度:衡量决策路径
圈复杂度通过计算代码中独立路径的数量来衡量复杂性。每个 if、for、while、try 等控制流语句都会增加圈复杂度。
想象一下在麦当劳点餐:每个选择(是否要套餐?要什么饮料?)都增加了决策的复杂性。代码也是如此,嵌套的条件判断会让阅读者需要在脑中模拟多个执行路径。
“扁平优于嵌套”是另一个重要的原则。扁平的代码结构通常更易于理解。
代码的“智慧”与历史负担
在尝试简化复杂代码时,需要注意:这些复杂性往往是为了解决特定的边界情况或历史遗留问题而引入的。直接删除它们可能会重新引入已修复的 bug。因此,在重构前,理解代码的演变历史至关重要。
第3章:更科学的代码度量 🔬
上一节我们介绍了圈复杂度,本节中我们来看看一些更理论化但更精确的度量方法。
这些度量基于“软件科学”理论,其核心是分析代码中使用的操作符和操作数。
- 操作数:代码中使用的变量、常量的总和。
- 操作符:代码中使用的关键字、运算符的总和。
以下是几个衍生出的核心度量公式:
- 程序长度:
长度 = 操作符总数 + 操作数总数 - 程序词汇量:
词汇量 = 唯一操作符数量 + 唯一操作数数量 - 程序体积:
体积 = 长度 * log2(词汇量)(这比单纯的行数更能反映代码“量”) - 程序难度:
难度 = (唯一操作符数 / 2) * (操作数总数 / 唯一操作数数) - 编程工作量:
工作量 = 难度 * 体积
可维护性指数
对于日常开发,一个更实用的综合指标是可维护性指数。它将体积、圈复杂度和代码行数结合,输出一个0到100的分数。
- 0-25:难以维护
- 25-50:需要关注
- 50-75:大多数应用程序的水平,有改进空间
- 75-100:非常易于维护
幸运的是,我们不需要手动计算这些指标。
第4章:使用工具量化复杂性 🛠️


上一节的理论可能有些复杂,本节我们介绍一个能自动完成这些计算的工具:radon 和 wily。
使用 radon 进行静态分析
radon 是一个 Python 库,可以直接在命令行中计算代码度量。
计算单个文件的圈复杂度:
radon cc your_script.py
计算可维护性指数:
radon mi your_script.py
使用 wily 跟踪复杂性趋势
wily 是一个更强大的工具,它能跟踪代码库在 Git 历史中的复杂性变化。
以下是 wily 的基本工作流程:


- 建立索引:分析 Git 历史中的所有版本。
wily build . - 生成报告:查看文件复杂性的变化。
wily report your_module.py - 差异对比:在提交前,查看本次修改对复杂性的影响(适合作为预提交钩子)。
wily diff - 图形化展示:可视化可维护性指数等指标随时间的变化。
wily graph your_module.py
通过 wily,你可以客观地看到每次提交是改善还是恶化了代码的可维护性,从而鼓励团队持续进行小规模重构。
第5章:简化代码的策略与原则 🧹

在能够衡量复杂性之后,我们来看看如何简化代码。
单一职责原则
这是简化代码的核心原则:一个函数、一个类或一个模块应该只负责一件事。避免创建处理所有事情的“上帝类”或“万能函数”。将大块的复杂逻辑拆分成职责单一的小模块,可以有效分散和降低整体复杂性。
重构前的安全网:测试
在重构任何代码之前,必须确保有良好的测试覆盖率。这就像在改造房屋前打好地基。测试能确保你的重构没有改变代码的原有行为。
但要注意,高行覆盖率不等于测试了所有行为。你需要确保测试用例覆盖了各种边界情况和业务场景。
谨慎处理已知的 Bug
在重构中,你可能会发现一些看似是 Bug,但实际上被其他代码依赖的“特性”。这时你面临三个选择:
- 在新代码中重现这个 Bug(保持兼容性)。
- 修复它,并向用户说明这是破坏性变更。
- (通常没有完美的第三个选项)
因此,充分理解代码行为和用户场景至关重要。
分而治之,持续重构
不要试图一次性重写整个庞大的代码库。这就像引入“甘蔗蟾蜍”来解决甲虫问题,可能带来更大的灾难。
正确的做法是:
- 从小处着手:从测试覆盖好、逻辑相对独立的模块开始重构。
- 持续进行:将重构作为开发流程的常规部分,而不是一年一次的大扫除。
- 利用工具:使用
wily这样的工具监控复杂性趋势,让团队看到重构的即时收益。
总结 🎓
在本教程中,我们一起学习了:
- 简单代码的价值:它更易于沟通、维护和测试。
- 复杂性的度量:从简单的代码行数到圈复杂度,再到更科学的 Halstead 度量和可维护性指数。
- 量化工具:如何使用
radon进行静态分析,以及如何使用wily跟踪代码库在整个生命周期中的复杂性变化。 - 简化策略:遵循单一职责原则,在充分测试的保护下进行重构,并采用“分而治之、持续进行”的策略来管理复杂性。

记住,编写代码是一个与人协作的过程。使用这些度量和工具,不是为了追求完美的分数,而是为了建立一个客观的反馈机制,帮助我们持续写出对人类和计算机都更加友好的代码。

浙公网安备 33010602011771号