阅读视图

发现新文章,点击刷新页面。

HelloGitHub 第 120 期

本期共有 40 个项目,包含 C 项目 (1),C# 项目 (4),C++ 项目 (2),Go 项目 (4),Java 项目 (3),JavaScript 项目 (5),Kotlin 项目 (2),Python 项目 (4),Rust 项目 (3),Swift 项目 (2),人工智能 (5),其它 (5)

前端仔转型之路 AI 应用开发

前言

作为一个写了 5 年 Vue + React 的前端,我用 Python + LangGraph 做了一个企业级 AIOps 排障 Agent。过程中最大的感悟是:真正难的不是调大模型,而是让大模型少干活。


这篇文章讲什么

我不会从头教你代码怎么写,因为这个年代工程思维才是价值。我要分享的是一个前端工程师做 AI Agent 项目的完整心路历程——从"LLM 是万能的"到"LLM 只在刀刃上用"的认知转变,以及这个过程中我踩的坑和总结的方法论。

如果你也是前端,也在考虑往 AI/全栈方向转,这篇文章可能对你有用。

文章结构:

  1. 为什么一个前端要做 Agent
  2. 架构设计:9 个节点只有 1 个用 LLM
  3. 最值钱的不是 LLM,是收敛窗口和限流器
  4. Redis 不是加分项,是必需品
  5. 前端思维在 Agent 开发中的意外优势
  6. 给想转型的前端小伙伴的建议

一、为什么一个前端要做 Agent

先说背景:我写了 5 年前端,技术栈以 Vue + React + TypeScript 为主,做的是 B 端企业级工具平台——从零搭建过组件库、主导过多个复杂中后台系统的架构设计,日常处理的就是大数据量表格虚拟滚动、复杂表单联动、权限路由体系、微前端接入这些硬骨头。随着AI浪潮发展,这几年也不是只写前端,因为做的是内部工具平台,经常需要自己写 Node.js 的 BFF 层对接后端微服务接口,所以对后端的 API 设计、数据库查询、中间件这些概念并不陌生。因此绝对不是后端零基础。

去年做了我的第一个 Agent 项目——AI 智能客服。那个项目让我入了门:学会了 Prompt 工程、RAG 检索增强、对话状态管理。后端部分用的 Python + FastAPI,也是那个项目让我正式从 Node.js 过渡到了 Python 技术栈。但说实话,智能客服的 Agent 逻辑相对简单——用户提问 → 检索知识库 → LLM 生成回答,基本是单轮或多轮对话,工程复杂度不高。

做完客服 Agent 后我一直在想:有没有更复杂的场景,能把 Agent 的工程化能力真正体现出来?

机会来了——一场普通的会议中:"每天 86 个服务的告警,光是看一遍就要 1 小时,分析根因又要半小时。"

我一听就知道,这个场景比客服复杂太多了:多数据源(指标 + 日志 + 机器状态)、多步推理(收敛 → 分类 → 采集 → 诊断)、成本敏感(每天几百次 LLM 调用)。正好是我想要的"第二个 Agent 项目"。

于是我主动请缨做了这个 AIOps 排障 Agent。第一版 MVP 花了两周跑通核心流程(收敛 → 分类 → 采集 → 诊断),先在 5 个核心服务上灰度验证。之后又用了一个多月做工程化优化(Redis 缓存层、事件驱动改造、限流和成本控制),逐步扩到全量 86 个微服务、2100 台服务器。最终 MTTR(平均修复时间)从 45 分钟降到 12 分钟。

下面我把第二个 Agent 项目中最核心的设计思路分享出来——很多是从第一个客服 Agent 踩坑后总结的教训。


二、架构设计:9 个节点只有 1 个用 LLM

第一版:全靠 LLM(客服 Agent 的惯性思维)

做客服 Agent 时,流程就是"用户问什么 → LLM 答什么",LLM 承担了几乎所有智能决策。我一开始做排障 Agent 时延续了这个思路:

告警进来 → 丢给 LLM → LLM 告诉我根因是什么 → 完事

一跑起来就出问题了——客服场景一天几百次对话还 hold 得住,运维场景一天几千条告警直接炸了

  • 太贵:一条告警要消耗 4000+ token,86 个服务每天几百条告警,一天 ¥85
  • 太慢:每次 LLM 调用 3-5 秒,所有环节都调 LLM 的话要 30 秒
  • 不可控:LLM 偶尔抽风给个完全离谱的分类,后面的诊断全跑偏

第二版:规则优先,LLM 只做规则做不了的事

客服 Agent 教会了我怎么跟 LLM 打交道,但排障 Agent 教会了我什么时候不该用 LLM

我重新审视了整个流程,发现一个关键事实:

80% 的决策是确定性的,不需要 LLM。

比如告警分类——如果 3 条告警里 2 条是 latency 类型,那事件类型就是"延迟异常",这用 Python 的 Counter.most_common 一行代码就能搞定,为什么要花钱让 LLM 来选?

最终架构是 LangGraph 状态机,9 个节点这样分工:

correlate    → 纯规则(按服务+依赖链收敛告警)
parse_input  → 纯规则(提取字段)
classify     → 80%规则 + 20%LLM 兜底
build_plan   → LLM 建议 + 规则强校验
run_tools    → 纯工具调用(查 Prometheus/ES/CMDB)
diagnose     → ⭐ 100% LLM(唯一核心)
risk_check   → 纯规则(关键词匹配"重启""回滚")
finalize     → 纯规则(置信度校准)
settle_case  → 纯规则(高置信度自动沉淀案例)

LLM 只在 diagnose 节点真正不可替代——因为"从错误率飙升 + ConnectionRefused 日志 + 刚部署新版本"这些线索推导出"新版本连接池配置错误",需要跨领域关联推理,规则写不了。

分类节点的代码,简单到让我意外

from collections import Counter

TYPE_MAP = {"error_rate": "error_rate", "latency": "latency", 
            "cpu": "resource", "memory": "resource"}

def classify_incident(state):
    # 投票:统计每种告警类型出现几次,取最多的
    counter = Counter(state["alert_types"])
    dominant = counter.most_common(1)[0][0]  # 比如 "latency"
    
    result = TYPE_MAP.get(dominant)  # 查规则表
    if result:
        return {"incident_type": result}  # 命中,不调 LLM
    
    return {"incident_type": _llm_classify(state)}  # 兜底

前端小伙伴应该觉得很眼熟——这跟条件渲染是一个逻辑:

if (type === 'error') return <ErrorIcon />;     // 规则命中
if (type === 'warning') return <WarningIcon />; // 规则命中
return <DefaultIcon />;                         // 兜底

效果对比

全 LLM 方案 规则 + LLM 混合
LLM 调用次数/诊断 5-9 次 1-2 次
Token 消耗 ~10,000 ~2,500
延迟 15-30 秒 3-5 秒
日均成本 ¥85 ¥32

一句话总结:不是 LLM 不好,是你不该让它做它不擅长的事。


三、最值钱的不是 LLM,是收敛窗口和限流器

告警风暴问题

上线第一天就出事了。

凌晨 2 点,一个服务挂了,5 分钟内产生了 47 条告警(error_rate、latency、cpu 同时飙)。按原来的设计,每条告警触发一次诊断,47 次 LLM 调用,一算要 ¥15——就这一个故障。

而且 47 次诊断结论都是一样的,因为根因就一个。

解决方案:2 分钟收敛窗口

我做了一个 AlertBuffer——同一个服务的告警先攒着,2 分钟后一起诊断:

def add(self, alert):
    key = (alert["project_id"], alert["service_name"])
    
    # 追加到 Redis 列表
    r.rpush(buffer_key, json.dumps(alert))
    
    # SETNX:只有第一条告警才启动定时器
    is_first = r.set(timer_key, "1", nx=True, ex=120)
    if is_first:
        # 120 秒后自动 flush
        Timer(120, self._flush, args=[key]).start()

前端小伙伴应该秒懂——这就是 debounce 的服务端版本

// 前端 debounce:停止输入 300ms 后才搜索
const debouncedSearch = debounce(search, 300);

// 后端 AlertBuffer:同服务告警 120s 后才诊断
alert_buffer.add(alert)  // 不立即诊断,等窗口到期

区别是前端 debounce 每次按键会重置计时器,而告警缓冲是固定窗口——第一条告警启动 120 秒倒计时,后续告警只追加不重置。更像 throttle + batch

47 条告警 → 1 次 LLM 调用。省了 46 次。

限流器:滑动窗口

LLM API 有 rate limit(每分钟 30 次),我用 Redis Sorted Set 做了滑动窗口限流:

def acquire(self, r):
    now = time.time()
    
    # ① 清理 60 秒前的记录
    r.zremrangebyscore(key, "-inf", now - 60)
    # ② 数当前窗口有多少次
    count = r.zcard(key)
    # ③ 没超限就放行
    if count < 30:
        r.zadd(key, {f"{now}:{thread_id}": now})
        return True
    return False

为什么不用简单计数器?因为固定窗口有边界突发问题:第 59 秒来 30 个请求 + 第 61 秒又来 30 个 = 2 秒内 60 个请求,但两个窗口各自都没超限。Sorted Set 滑动窗口保证任意连续 60 秒内不超过 30 次


四、Redis 不是加分项,是必需品

一开始我什么都存在内存里——缓冲队列用 dict,缓存用 dict,限流用 deque

看起来能跑,直到我问自己:

"服务重启了,正在收敛的告警怎么办?" "多部署一个实例,限流计数器各算各的,总量不就超了?"

答案是:必须用 Redis

但我做了一个关键设计——Redis 优先,自动降级到内存

def add(self, alert):
    r = get_redis()          # 尝试获取 Redis 连接
    if r is not None:
        self._add_to_redis(r, alert)   # Redis 可用 → 用 Redis
    else:
        self._add_to_memory(alert)     # Redis 挂了 → 降级到内存

为什么不直接强依赖 Redis?

AIOps 排障场景,可用性比一致性重要。 Redis 挂了,宁可用内存顶着(可能重启丢数据),也不能因为缓存不可用就不诊断。

最终 Redis 用了 4 个场景:

场景 Redis 数据结构 为什么
告警缓冲队列 List (RPUSH) 重启不丢、多实例共享
LLM 结果缓存 String + TTL 相同告警不重复调 LLM
调用限流 Sorted Set 多实例共享计数
每日 Token 预算 INCRBY 原子累加、次日自动清零

五、前端思维在 Agent 开发中的意外优势

做完这个项目,我发现前端经验给了我几个别人没有的优势:

1. 状态管理思维 → Agent 状态机

LangGraph 的 AgentState 本质就是一个全局状态树,9 个节点像 reducer 一样处理状态:

# Agent 的 state 流转
{"alert_messages": [...], "incident_type": None}
    → correlate → {"alert_messages": [...更多], ...}
    → classify  → {"incident_type": "latency", ...}
    → diagnose  → {"root_causes": [...], ...}

这跟 Redux 一模一样:

// Redux 的 state 流转
{alerts: [], type: null}
    → FETCH_ALERTS → {alerts: [...], ...}
    → CLASSIFY     → {type: 'latency', ...}
    → DIAGNOSE     → {rootCauses: [...], ...}

2. 组件化思维 → 节点解耦

前端写组件讲究"单一职责、props 进 events 出"。Agent 节点也一样——每个节点只从 state 里取自己需要的字段,处理完返回新字段,不关心其他节点。

这让我天然就把节点写得很解耦,后来加新功能(比如 risk_check)只需要新增一个节点文件 + 在图里加一条边。

3. 降级思维 → 容错设计

前端天天跟"接口挂了怎么办"打交道(loading → error → empty 三态)。写 Agent 时我本能地给每个节点都加了降级:

  • LLM 挂了 → 返回保守结论(confidence: 0.3)
  • Redis 挂了 → 降级到内存
  • 工具查询失败 → 跳过,用已有证据继续

有些纯后端/AI 背景的人会忽略这些,因为他们习惯"调不通就报错退出"。

4. Debounce/Throttle → 收敛窗口

上面讲的告警收敛,本质就是 debounce。前端经常写这个,我看到"告警风暴"的问题第一反应就是"加个 debounce",而后端小伙伴可能会想到 Kafka 之类的重方案。


六、给想转型的前端小伙伴的建议

1. 不要从零学 Python,从"翻译"开始

我学 Python 的方式不是看教程,而是把我熟悉的 JS 逻辑翻译成 Python

// JS: 数组去重
const unique = [...new Set(arr)];

// JS: 条件渲染
const icon = type === 'error' ? <ErrorIcon /> : <DefaultIcon />;

// JS: debounce
const debounced = debounce(fn, 300);

翻译成 Python:

# Python: 列表去重
unique = list(set(arr))

# Python: 条件分支
icon = ErrorIcon if type == 'error' else DefaultIcon

# Python: Timer(类似 setTimeout)
Timer(120, fn).start()

语法不同,但编程思维是通用的

2. Agent 的核心不是 LLM,是工程化

很多人觉得做 AI Agent 就是"调 LLM API"。不是的。

我这个项目里 LLM 相关代码大概占 15%。另外 85% 是:

  • 告警收敛逻辑(状态管理)
  • Redis 缓存和限流(中间件)
  • 数据源抽象层(设计模式)
  • 并发调度(线程池)
  • 错误处理和降级(容错)

这些全是工程能力,跟 LLM 无关,跟前端经验高度相关。

3. 选一个你熟悉的业务场景做 Agent

不要凭空造需求。找一个你日常工作中有的痛点:

  • 做运维平台的 → AIOps 排障 Agent(就是我做的这个)
  • 做客服系统的 → 智能客服 Agent
  • 做数据平台的 → 自动化数据分析 Agent
  • 做文档系统的 → RAG 知识库 Agent

业务理解 + 工程能力 + AI 能力 = 你的不可替代性。


小结

做完这个项目,我最大的感悟是:

AI Agent 开发 = 10% 的 LLM + 90% 的工程化。

LLM 是那个做"最后一公里"推理的天才,但天才需要一个靠谱的团队帮他准备好数据、控制好成本、兜住错误。这个"团队"就是你写的工程代码。

而前端工程师天然擅长这些——状态管理、组件化、降级容错、性能优化——只是以前这些能力用在了浏览器里,现在换到了服务端而已。

如果你也是前端,也想往 AI 方向试试,别犹豫。工程能力比纯写代码能力值钱得多了,因为你永远没有AI会写代码。

Python f-Strings: String Formatting in Python 3

f-strings, short for formatted string literals, are the most concise and readable way to format strings in Python. Introduced in Python 3.6, they let you embed variables and expressions directly inside a string by prefixing it with f or F.

This guide explains how to use f-strings in Python, including expressions, format specifiers, number formatting, alignment, and the debugging shorthand introduced in Python 3.8.

Basic Syntax

An f-string is a string literal prefixed with f. Any expression inside curly braces {} is evaluated at runtime and its result is inserted into the string:

py
name = "Leia"
age = 30
print(f"My name is {name} and I am {age} years old.")
output
My name is Leia and I am 30 years old.

Any valid Python expression can appear inside the braces — variables, attribute access, method calls, or arithmetic:

py
price = 49.99
quantity = 3
print(f"Total: {price * quantity}")
output
Total: 149.97

Expressions Inside f-Strings

You can call methods and functions directly inside the braces:

py
name = "alice"
print(f"Hello, {name.upper()}!")
output
Hello, ALICE!

Ternary expressions work as well:

py
age = 20
print(f"Status: {'adult' if age >= 18 else 'minor'}")
output
Status: adult

You can also access dictionary keys and list items:

py
user = {"name": "Bob", "city": "Berlin"}
print(f"{user['name']} lives in {user['city']}.")
output
Bob lives in Berlin.

Escaping Braces

To include a literal curly brace in an f-string, double it:

py
value = 42
print(f"{{value}} = {value}")
output
{value} = 42

Multiline f-Strings

To build a multiline f-string, wrap it in triple quotes:

py
name = "Leia"
age = 30
message = f"""
Name: {name}
Age: {age}
"""
print(message)
output
Name: Leia
Age: 30

Alternatively, use implicit string concatenation to keep each line short:

py
message = (
 f"Name: {name}\n"
 f"Age: {age}"
)

Format Specifiers

f-strings support Python’s format specification mini-language. The full syntax is:

txt
{value:[fill][align][sign][width][grouping][.precision][type]}

Floats and Precision

Control the number of decimal places with :.Nf:

py
pi = 3.14159265
print(f"{pi:.2f}")
print(f"{pi:.4f}")
output
3.14
3.1416

Thousands Separator

Use , to add a thousands separator:

py
population = 1_234_567
print(f"{population:,}")
output
1,234,567

Percentage

Use :.N% to format a value as a percentage:

py
score = 0.875
print(f"Score: {score:.1%}")
output
Score: 87.5%

Scientific Notation

Use :e for scientific notation:

py
distance = 149_600_000
print(f"{distance:e}")
output
1.496000e+08

Alignment and Padding

Use <, >, and ^ to align text within a fixed width. Optionally specify a fill character before the alignment symbol:

py
print(f"{'left':<10}|")
print(f"{'right':>10}|")
print(f"{'center':^10}|")
print(f"{'padded':*^10}|")
output
left |
right|
center |
**padded**|

Number Bases

Convert integers to binary, octal, or hexadecimal with the b, o, x, and X type codes:

py
n = 255
print(f"Binary: {n:b}")
print(f"Octal: {n:o}")
print(f"Hex (lower): {n:x}")
print(f"Hex (upper): {n:X}")
output
Binary: 11111111
Octal: 377
Hex (lower): ff
Hex (upper): FF

Debugging with =

Python 3.8 introduced the = shorthand, which prints both the expression and its value. This is useful for quick debugging:

py
x = 42
items = [1, 2, 3]
print(f"{x=}")
print(f"{len(items)=}")
output
x=42
len(items)=3

The = shorthand preserves the exact expression text, making it more informative than a plain print().

Quick Reference

Syntax Description
f"{var}" Insert a variable
f"{expr}" Insert any expression
f"{val:.2f}" Float with 2 decimal places
f"{val:,}" Integer with thousands separator
f"{val:.1%}" Percentage with 1 decimal place
f"{val:e}" Scientific notation
f"{val:<10}" Left-align in 10 characters
f"{val:>10}" Right-align in 10 characters
f"{val:^10}" Center in 10 characters
f"{val:*^10}" Center with * fill
f"{val:b}" Binary
f"{val:x}" Hexadecimal (lowercase)
f"{val=}" Debug: print expression and value (3.8+)

FAQ

What Python version do f-strings require?
f-strings were introduced in Python 3.6. If you are on Python 3.5 or earlier, use str.format() or % formatting instead.

What is the difference between f-strings and str.format()?
f-strings are evaluated at runtime and embed expressions inline. str.format() uses positional or keyword placeholders and is slightly more verbose. f-strings are generally faster and easier to read for straightforward formatting.

Can I use quotes inside an f-string expression?
Yes, but you must use a different quote type from the one wrapping the f-string. For example, if the f-string uses double quotes, use single quotes inside the braces: f"Hello, {user['name']}". In Python 3.12 and later, the same quote type can be reused inside the braces.

Can I use f-strings with multiline expressions?
Expressions inside {} cannot contain backslashes directly, but you can pre-compute the value in a variable first and reference that variable in the f-string.

Are f-strings faster than % formatting?
In many common cases, f-strings are faster than % formatting and str.format(), while also being easier to read. Exact performance depends on the expression and Python version, so readability is usually the more important reason to prefer them.

Conclusion

f-strings are the preferred way to format strings in Python 3.6 and later. They are concise, readable, and support the full format specification mini-language for controlling number precision, alignment, and type conversion. For related string operations, see Python String Replace and How to Split a String in Python .

Python Virtual Environments: venv and virtualenv

A Python virtual environment is a self-contained directory that includes its own Python interpreter and a set of installed packages, isolated from the system-wide Python installation. Using virtual environments lets each project maintain its own dependencies without affecting other projects on the same machine.

This guide explains how to create and manage virtual environments using venv (built into Python 3) and virtualenv (a popular third-party alternative) on Linux and macOS.

Quick Reference

Task Command
Install venv support (Ubuntu, Debian) sudo apt install python3-venv
Create environment python3 -m venv venv
Activate (Linux / macOS) source venv/bin/activate
Deactivate deactivate
Install a package pip install package-name
Install specific version pip install package==1.2.3
List installed packages pip list
Save dependencies pip freeze > requirements.txt
Install from requirements file pip install -r requirements.txt
Create with specific Python version python3.11 -m venv venv
Install virtualenv pip install virtualenv
Delete environment rm -rf venv

What Is a Python Virtual Environment?

When you install a package globally with pip install, it is available to every Python script on the system. This becomes a problem when two projects need different versions of the same library — for example, one requires requests==2.28.0 and another requires requests==2.31.0. Installing both globally is not possible.

A virtual environment solves this by giving each project its own isolated space for packages. Activating an environment prepends its bin/ directory to your PATH, so python and pip resolve to the versions inside the environment rather than the system-wide ones.

Installing venv

The venv module ships with Python 3 and requires no separate installation on most systems. On Ubuntu and Debian, the module is packaged separately:

Terminal
sudo apt install python3-venv

On Fedora, RHEL, and Derivatives, venv is included with the Python package by default.

Verify your Python version before creating an environment:

Terminal
python3 --version

For more on checking your Python installation, see How to Check Python Version .

Creating a Virtual Environment

Navigate to your project directory and run:

Terminal
python3 -m venv venv

The second venv is the name of the directory that will be created. The conventional names are venv or .venv. The directory contains a copy of the Python binary, pip, and the standard library.

To create the environment in a specific location rather than the current directory:

Terminal
python3 -m venv ~/projects/myproject/venv

Activating the Virtual Environment

Before using the environment, you need to activate it.

On Linux and macOS:

Terminal
source venv/bin/activate

Once activated, your shell prompt changes to show the environment name:

output
(venv) user@host:~/myproject$

From this point on, python and pip refer to the versions inside the virtual environment, not the system-wide ones.

To deactivate the environment and return to the system Python:

Terminal
deactivate

Installing Packages

With the environment active, install packages using pip as you normally would. If pip is not installed on your system, see How to Install Python Pip on Ubuntu .

Terminal
pip install requests

To install a specific version:

Terminal
pip install requests==2.31.0

To list all packages installed in the current environment:

Terminal
pip list

Packages installed here are completely isolated from the system and from other virtual environments.

Managing Dependencies with requirements.txt

Sharing a project with others, or deploying it to another machine, requires a way to reproduce the same package versions. The convention is to save all dependencies to a requirements.txt file:

Terminal
pip freeze > requirements.txt

The file lists every installed package and its exact version:

output
certifi==2024.2.2
charset-normalizer==3.3.2
idna==3.6
requests==2.31.0
urllib3==2.2.1

To install all packages from the file in a fresh environment:

Terminal
pip install -r requirements.txt

This is the standard workflow for reproducing an environment on a different machine or in a CI/CD pipeline.

Using a Specific Python Version

By default, python3 -m venv uses whichever Python 3 version is the system default. If you have multiple Python versions installed, you can specify which one to use:

Terminal
python3.11 -m venv venv

This creates an environment based on Python 3.11 regardless of the system default. To check which versions are available :

Terminal
python3 --version
python3.11 --version

virtualenv: An Alternative to venv

virtualenv is a third-party package that predates venv and offers a few additional features. Install it with pip:

Terminal
pip install virtualenv

Creating and activating an environment with virtualenv follows the same pattern:

Terminal
virtualenv venv
source venv/bin/activate

To use a specific Python interpreter:

Terminal
virtualenv -p python3.11 venv

When to use virtualenv over venv:

  • You need to create environments faster — virtualenv is significantly faster on large projects.
  • You are working with Python 2 (legacy codebases only).
  • You need features like --copies to copy binaries instead of symlinking.

For most modern Python 3 projects, venv is sufficient and has the advantage of requiring no installation.

Excluding the Environment from Version Control

The virtual environment directory should never be committed to version control. It is large, machine-specific, and fully reproducible from requirements.txt. Add it to your .gitignore:

Terminal
echo "venv/" >> .gitignore

If you named your environment .venv instead, add .venv/ to .gitignore.

Troubleshooting

python3 -m venv venv fails with “No module named venv”
On Ubuntu and Debian, the venv module is not included in the base Python package. Install it with sudo apt install python3-venv, then retry.

pip install installs packages globally instead of into the environment
The environment is not activated. Run source venv/bin/activate first. You can verify by checking which pip — it should point to a path inside your venv/ directory.

“command not found: python” after activating the environment
The environment uses python3 as the binary name if it was created with python3 -m venv. Use python3 explicitly, or check which python and which python3 inside the active environment.

The environment breaks after moving or renaming its directory
Virtual environments contain hardcoded absolute paths to the Python interpreter. Moving or renaming the directory invalidates those paths. Delete the directory and recreate the environment in the new location, then reinstall from requirements.txt.

FAQ

What is the difference between venv and virtualenv?
venv is a standard library module included with Python 3 — no installation needed. virtualenv is a third-party package with a longer history, faster environment creation, and Python 2 support. For new Python 3 projects, venv is the recommended choice.

Should I commit the virtual environment to git?
No. Add venv/ (or .venv/) to your .gitignore and commit only requirements.txt. Anyone checking out the project can recreate the environment with pip install -r requirements.txt.

Do I need a virtual environment for every project?
It is strongly recommended. Without isolated environments, installing or upgrading a package for one project can break another. The overhead of creating a virtual environment is minimal.

What is the difference between pip freeze and pip list?
pip list shows installed packages in a human-readable format. pip freeze outputs them in requirements.txt format (package==version) suitable for use with pip install -r.

Can I use a virtual environment with a different Python version than the system default?
Yes. Pass the path to the desired interpreter when creating the environment: python3.11 -m venv venv. The environment will use that interpreter for both python and pip commands.

Conclusion

Virtual environments are the standard way to manage Python project dependencies. Create one with python3 -m venv venv, activate it with source venv/bin/activate, and use pip freeze > requirements.txt to capture your dependencies. For more on managing Python installations, see How to Check Python Version .

HelloGitHub 第 119 期

本期共有 41 个项目,包含 C 项目 (2),C# 项目 (2),C++ 项目 (2),Go 项目 (4),Java 项目 (2),JavaScript 项目 (5),Kotlin 项目 (2),Python 项目 (5),Rust 项目 (3),Swift 项目 (3),人工智能 (6),其它 (5)

Python Switch Case Statement (match-case)

Unlike many other programming languages, Python does not have a traditional switch-case statement. Before Python 3.10, developers used if-elif-else chains or dictionary lookups to achieve similar functionality.

Python 3.10 introduced the match-case statement (also called structural pattern matching), which provides a cleaner way to handle multiple conditions.

This article explains how to implement switch-case behavior in Python using all three approaches with practical examples.

Using if-elif-else

The if-elif-else chain is the most straightforward way to handle multiple conditions. It works in all Python versions and is best suited for a small number of conditions.

Here is an example:

py
def get_day_type(day):
 if day == "Saturday" or day == "Sunday":
 return "Weekend"
 elif day == "Monday":
 return "Start of the work week"
 elif day == "Friday":
 return "End of the work week"
 else:
 return "Midweek"

print(get_day_type("Saturday"))
print(get_day_type("Monday"))
print(get_day_type("Wednesday"))
output
Weekend
Start of the work week
Midweek

The function checks each condition in order. When a match is found, it returns the result and stops. If none of the conditions match, the else block runs.

This approach is easy to read and debug, but it gets verbose when you have many conditions.

Using Dictionary Lookup

A dictionary can map values to results or functions, acting as a lookup table. This approach is more concise than if-elif-else when you have many simple mappings.

In the following example, we are using a dictionary to map HTTP status codes to their descriptions:

py
def http_status(code):
 statuses = {
 200: "OK",
 301: "Moved Permanently",
 404: "Not Found",
 500: "Internal Server Error",
 }
 return statuses.get(code, "Unknown Status")

print(http_status(200))
print(http_status(404))
print(http_status(999))
output
OK
Not Found
Unknown Status

The get() method returns the value for the given key. If the key is not found, it returns the default value ("Unknown Status").

You can also map keys to functions. In the code below, we are creating a simple calculator using a dictionary of functions:

py
def add(a, b):
 return a + b

def subtract(a, b):
 return a - b

def multiply(a, b):
 return a * b

operations = {
 "+": add,
 "-": subtract,
 "*": multiply,
}

func = operations.get("+")
print(func(10, 5))
output
15

Dictionary lookups are fast and scale well, but they cannot handle complex conditions like ranges or pattern matching.

Using match-case (Python 3.10+)

The match-case statement was introduced in Python 3.10 . It compares a value against a series of patterns and executes the matching block.

The match-case statement takes the following form:

py
match expression:
 case pattern1:
 statements
 case pattern2:
 statements
 case _:
 default statements

The match keyword is followed by the expression to evaluate. Each case defines a pattern to match against. The case _ is the wildcard (default) case that matches any value not matched by previous patterns, similar to default in other languages.

Here is a basic example:

py
def http_error(status):
 match status:
 case 400:
 return "Bad Request"
 case 401 | 403:
 return "Not Allowed"
 case 404:
 return "Not Found"
 case _:
 return "Unknown Error"

print(http_error(403))
print(http_error(404))
print(http_error(999))
output
Not Allowed
Not Found
Unknown Error

You can combine multiple values in a single case using the | (or) operator, as shown with 401 | 403.

Let’s look at the different pattern matching capabilities of the match-case statement.

Matching with Variables

You can capture values from the matched expression and use them in the case block. In the following example, we are matching a tuple representing a point on a coordinate plane:

py
point = (3, 7)

match point:
 case (0, 0):
 print("Origin")
 case (x, 0):
 print(f"On x-axis at {x}")
 case (0, y):
 print(f"On y-axis at {y}")
 case (x, y):
 print(f"Point at ({x}, {y})")
output
Point at (3, 7)

The variables x and y are assigned the values from the tuple when the pattern matches.

Matching with Guards

You can add an if condition (called a guard) to a case pattern for more precise matching:

py
def classify_age(age):
 match age:
 case n if n < 0:
 return "Invalid"
 case n if n < 18:
 return "Minor"
 case n if n < 65:
 return "Adult"
 case _:
 return "Senior"

print(classify_age(10))
print(classify_age(30))
print(classify_age(70))
output
Minor
Adult
Senior

The guard (if n < 18) adds an extra condition that must be true for the case to match.

Matching Dictionaries

The match-case statement can match against dictionary structures, which is useful when working with JSON data or API responses:

py
def process_command(command):
 match command:
 case {"action": "create", "name": name}:
 print(f"Creating {name}")
 case {"action": "delete", "name": name}:
 print(f"Deleting {name}")
 case {"action": action}:
 print(f"Unknown action: {action}")

process_command({"action": "create", "name": "users"})
process_command({"action": "delete", "name": "logs"})
process_command({"action": "update"})
output
Creating users
Deleting logs
Unknown action: update

The pattern only needs to match the specified keys. Extra keys in the dictionary are ignored.

Which Approach to Use

Approach Best For Python Version
if-elif-else Few conditions, complex boolean logic All versions
Dictionary lookup Many simple value-to-value mappings All versions
match-case Pattern matching, destructuring, complex data 3.10+

Use if-elif-else when you have a handful of conditions or need complex boolean expressions. Use dictionary lookups when you are mapping values directly. Use match-case when you need to match against data structures, capture variables, or use guard conditions.

Quick Reference

py
# if-elif-else
if x == 1:
 result = "one"
elif x == 2:
 result = "two"
else:
 result = "other"

# Dictionary lookup
result = {1: "one", 2: "two"}.get(x, "other")

# match-case (Python 3.10+)
match x:
 case 1:
 result = "one"
 case 2:
 result = "two"
 case _:
 result = "other"

FAQ

Does Python have a switch statement?
Not a traditional one. Python uses if-elif-else chains, dictionary lookups, or the match-case statement (Python 3.10+) to achieve similar functionality.

What Python version do I need for match-case?
Python 3.10 or later. If you need to support older versions, use if-elif-else or dictionary lookups instead.

Is match-case faster than if-elif-else?
For most use cases the performance difference is negligible. Choose based on readability and the complexity of your conditions, not speed. Dictionary lookups are often the fastest for simple value mappings.

What happens if no case matches and there is no wildcard?
Nothing. If no pattern matches and there is no case _, the match statement completes without executing any block. No error is raised.

Can I use match-case with classes?
Yes. You can match against class instances using the case ClassName(attr=value) syntax. This is useful for handling different object types in a clean way.

Conclusion

Python does not have a built-in switch statement, but offers three alternatives. Use if-elif-else for simple conditions, dictionary lookups for direct value mappings, and match-case for pattern matching on complex data structures.

For more Python tutorials, see our guides on for loops , while loops , and dictionaries .

If you have any questions, feel free to leave a comment below.

How to Install Python on Ubuntu 24.04

Python is one of the most popular programming languages. It is used to build all kinds of applications, from simple scripts to complex machine-learning systems. With its straightforward syntax, Python is a good choice for both beginners and experienced developers.

Ubuntu 24.04 ships with Python 3.12 preinstalled. To check the version on your system:

Terminal
python3 --version
output
Python 3.12.3

If you need a newer Python version such as 3.13 or 3.14, you can install it from the deadsnakes PPA or build it from source. Both methods install the new version alongside the system Python without replacing it. This guide shows how to install Python on Ubuntu 24.04 using both approaches.

Quick Reference

Task Command
Check installed Python version python3 --version
Add deadsnakes PPA sudo add-apt-repository ppa:deadsnakes/ppa
Install Python 3.13 from PPA sudo apt install python3.13
Install Python 3.14 from PPA sudo apt install python3.14
Install venv module sudo apt install python3.13-venv
Build from source (configure) ./configure --enable-optimizations
Build from source (compile) make -j $(nproc)
Install from source sudo make altinstall
Create a virtual environment python3.13 -m venv myproject
Activate virtual environment source myproject/bin/activate
Deactivate virtual environment deactivate

Installing Python from the Deadsnakes PPA

The deadsnakes PPA provides newer Python versions packaged for Ubuntu. This is the easiest way to install a different Python version.

  1. Install the prerequisites and add the PPA:

    Terminal
    sudo apt update
    sudo apt install software-properties-common
    sudo add-apt-repository ppa:deadsnakes/ppa

    Press Enter when prompted to confirm.

  2. Install Python 3.13:

    Terminal
    sudo apt update
    sudo apt install python3.13

    To install Python 3.14 instead, replace python3.13 with python3.14 in the command above.

  3. Verify the installation:

    Terminal
    python3.13 --version
    output
    Python 3.13.11

    You can also confirm the binary location:

    Terminal
    which python3.13
  4. Install the venv module (needed for creating virtual environments):

    Terminal
    sudo apt install python3.13-venv
  5. If you need pip, install the venv package and create a virtual environment, then use pip inside the venv:

    Terminal
    python3.13 -m venv myproject
    source myproject/bin/activate
    python -m pip install --upgrade pip

    If you need a system-level pip for that interpreter, run:

    Terminal
    python3.13 -m ensurepip --upgrade
Info
The system default python3 still points to Python 3.12. To use the newly installed version, run python3.13 or python3.14 explicitly.

Installing Python from Source

Compiling Python from source allows you to install any version and customize the build options. However, you will not be able to manage the installation through the apt package manager.

The following steps show how to compile Python 3.13. If you are installing a different version, replace the version number in the commands below.

  1. Install the libraries and dependencies required to build Python:

    Terminal
    sudo apt update
    sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev liblzma-dev
  2. Download the source code from the Python download page using wget :

    Terminal
    wget https://www.python.org/ftp/python/3.13.11/Python-3.13.11.tgz
  3. Extract the archive :

    Terminal
    tar -xf Python-3.13.11.tgz
  4. Navigate to the source directory and run the configure script:

    Terminal
    cd Python-3.13.11
    ./configure --enable-optimizations

    The --enable-optimizations flag runs profile-guided optimization tests, which makes the build slower but produces a faster Python binary.

  5. Start the build process:

    Terminal
    make -j $(nproc)

    The -j $(nproc) option uses all available CPU cores for a faster build.

  6. Install the Python binaries using altinstall. Do not use install, as it overwrites the system python3 binary and can break system tools that depend on it:

    Terminal
    sudo make altinstall
  7. Verify the installation:

    Terminal
    python3.13 --version
    output
    Python 3.13.11

Setting Up a Virtual Environment

After installing a new Python version, create a virtual environment for your project to keep dependencies isolated:

Terminal
python3.13 -m venv myproject

Activate the virtual environment:

Terminal
source myproject/bin/activate

Your shell prompt will change to show the environment name. Inside the virtual environment, python and pip point to the version you used to create it.

To deactivate the virtual environment:

Terminal
deactivate

For more details, see our guide on how to create Python virtual environments .

Uninstalling the PPA Version (Optional)

To remove the PPA version:

Terminal
sudo apt remove python3.13 python3.13-venv

To remove the PPA itself:

Terminal
sudo add-apt-repository --remove ppa:deadsnakes/ppa

FAQ

Should I use the PPA or build from source?
The deadsnakes PPA is the recommended method for most users. It is easier to install, receives security updates through apt, and does not require build tools. Build from source only if you need a custom build configuration or a version not available in the PPA.

Will installing a new Python version break my system?
No. Both methods install the new version alongside the system Python 3.12. The system python3 command is not affected. You access the new version with python3.13 or python3.14.

How do I make the new Python version the default?
You can use update-alternatives to configure it, but this is not recommended. Many Ubuntu system tools depend on the default python3 being the version that shipped with the OS. Use virtual environments instead.

How do I install pip for the new Python version?
The recommended approach is to use a virtual environment. Install python3.13-venv, create a venv, and use pip inside it. If you need a system-level pip for that interpreter, run python3.13 -m ensurepip --upgrade.

What is the difference between install and altinstall when building from source?
altinstall installs the binary as python3.13 without creating a python3 symlink. install creates the symlink, which overwrites the system Python and can break Ubuntu system tools.

Does Ubuntu 24.04 include pip by default?
Ubuntu 24.04 includes Python 3.12 but does not include pip in the base installation. Install it with sudo apt install python3-pip. For newer Python versions, use python3.13 -m pip inside a virtual environment.

Conclusion

Ubuntu 24.04 ships with Python 3.12. To install a newer version, use the deadsnakes PPA for a simple apt-based installation, or build from source for full control over the build. Use virtual environments to manage project dependencies without affecting the system Python.

For more on Python package management, see our guide on how to use pip .

If you have any questions, feel free to leave a comment below.

❌