Skip to content

Commit 719156f

Browse files
committed
post: python generics
1 parent b430d15 commit 719156f

File tree

14 files changed

+914
-37
lines changed

14 files changed

+914
-37
lines changed

content/posts/microservice-apis.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,161 @@ class OrderItemSchema(BaseModel):
243243
```
244244

245245
### Marshalling and validating response payloads with pydantic
246+
这里定义一下返回类型 [api.py](https://github.com/abunuwas/microservice-apis/blob/master/ch02/orders/api/api.py)
247+
```Python
248+
from starlette.responses import Response
249+
from starlette import status
250+
from orders.api.schemas import (
251+
GetOrderSchema,
252+
CreateOrderSchema,
253+
GetOrdersSchema,
254+
)
255+
256+
@app.get("/orders", response_model=GetOrdersSchema)
257+
def get_orders():
258+
return [
259+
order
260+
]
261+
262+
@app.post(
263+
"/orders",
264+
status_code=status.HTTP_201_CREATED,
265+
response_model=GetOrderSchema
266+
)
267+
def create_order(order_details: CreateOrderSchema):
268+
return order
269+
```
270+
现在, 如果 response payload 中缺少了返回类型需要的属性, FastAPI 则会报错, 如果有多的属性, 则会被去除
271+
272+
273+
### Adding an in-memory list of orders to the API
274+
现在通过一个简单的内存列表来管理订单状态
275+
```Python
276+
import time
277+
import uuid
278+
from datetime import datetime
279+
from uuid import UUID
280+
281+
from fastapi import HTTPException
282+
from starlette.responses import Response
283+
from starlette import status
284+
285+
from orders.app import app
286+
from orders.api.schemas import GetOrderSchema, CreateOrderSchema
287+
288+
ORDERS = [] # in memory list
289+
290+
# 获取订单列表
291+
@app.get("/orders", respones_model=GetOrderSchema)
292+
def get_orders():
293+
return ORDERS # return order list
294+
295+
# 创建订单
296+
@app.post(
297+
"/orders",
298+
status_code=status.HTTP_201_CREATED,
299+
response_model=GetOrderSchema,
300+
)
301+
def create_order(order_details: CreateOrderSchema):
302+
# convert Pydantic model -> dict: v1 use .dict(); v2 use .model_dump()
303+
order = order_details.model_dump()
304+
order["id"] = uuid.uuid4()
305+
306+
# 获取订单
307+
@app.get("/orders/{order_id}", response_model=GetOrderSchema)
308+
def get_order(order_id: UUID):
309+
for order in ORDERS:
310+
if order["id"] == order_id:
311+
return order
312+
raise HTTPException(
313+
status_code=404,
314+
detail=f"Order with ID {order_id} not found",
315+
)
316+
317+
# 更新订单
318+
@app.put("/orders/{order_id}", response_model=GetOrderSchema)
319+
def update_order(order_id: UUID, order_details: CreateOrderSchema):
320+
for order in ORDERS:
321+
if order["id"] == order_id:
322+
order.update(order_details.model_dump())
323+
return order
324+
raise HTTPException(
325+
status_code=404,
326+
detail=f"Order with ID {order_id} not found",
327+
)
328+
329+
# 删除订单
330+
@app.delete(
331+
"/orders/{order_id}",
332+
status_code=status.HTTP_204_NO_CONTENT,
333+
response_class=Response
334+
)
335+
def delete_order(order_id: UUID):
336+
for index, order in enumerate(ORDERS):
337+
if order["id"] == order_id:
338+
ORDERS.pop(index)
339+
return Response(status_code=HTTPStatus.NO_CONTENT.value)
340+
raise HTTPException(
341+
status_code=404,
342+
detail=f"Order with ID {order_id} not found",
343+
)
344+
345+
# 取消订单
346+
@app.post("/orders/{order_id}/cancel", response_model=GetOrderSchema)
347+
def cancel_order(order_id: UUID):
348+
for order in ORDERS:
349+
if order["id"] == order_id:
350+
order["status"] = "cancelled"
351+
return order
352+
raise HTTPException(
353+
status_code=404,
354+
detail=f"Order with ID {order_id} not found",
355+
)
356+
357+
# 支付订单
358+
@app.get("/orders/{order_id}/pay", response_model=GetOrderSchema)
359+
def pay_order(order_id: UUID):
360+
for order in ORDERS:
361+
if order["id"] == order_id:
362+
order["status"] = "progress"
363+
return order
364+
raise HTTPException(
365+
status_code=404,
366+
detail=f"Order with ID {order_id} not found"<>
367+
)
368+
```
369+
370+
371+
### Microservice Principles
372+
微服务设计原则: 如何将系统拆分为微服务 *serivce decomposition*, 以及如何估计其质量
373+
下面是三个设计原则:
374+
- Database-per-service principle 服务独立数据库原则
375+
- Loose coupling principle 松耦合原则
376+
- Single Responsibility Principle (SRP) 单一职责原则
377+
378+
遵循这些原则将帮助你避免构建一个"分布式单体应用"(distributed monolith)的风险
379+
380+
#### Data-per-service principle
381+
服务独立数据库原则是指, 每个微服务拥有一系列具体的数据, 并且其他微服务只能通过 API 访问.
382+
383+
这并不意味着每个微服务都要连接到不同的数据库中, 可以是关系数据库中的不同 tables, 或者非关系数据库中的 collections, 关键是数据被某个服务拥有, 不能被其他服务直接访问.
384+
385+
例如, 为了计算价格, orders service 从 Production database 中获取每个物品的价格, 它也需要知道用户是否有折扣优惠, 这个需要从 User database 获取. 然而, 不能直接诶访问这两个数据库, order service 需要从 products service 和 users service 获取数据.
386+
387+
#### Loose coupling principle
388+
松耦合原则要求在设计服务的时候, 必须清晰的关注分离点, 松耦合的服务不依赖另一个服务的实现细节, 这项原则有两个实际的应用:
389+
- 每个服务都可以独立于其他服务工作: 如果一个服务在不调用另一个服务的情况下无法完成一个简单的请求, 那么这两个服务之间没有清晰的关注点分离, 他们应被视为一个整体
390+
- 每个服务都可以在不影响其他服务工作的情况下进行更新: 如果一个服务的更新需要其他服务, 那么这些服务之间存在紧密耦合, 需要重新设计
391+
392+
例如, 一个基于历史数据计算销售预测的服务(Sales Forecast Service), 以及一个拥有历史销售数据的服务(Historical Data Service), 为了计算预测, 销售服务会调用历史数据服务的API来获取历史数据. 在这种情况下, 销售预测服务在不调用历史数据服务的情况下无法响应任何请求, 因此两个服务之间存在紧密耦合.
393+
394+
解决方案是重新设计这两个服务, 使它们不相互依赖, 或者将它们合并成一个单一的服务.
395+
396+
397+
#### Single responsibility principle
398+
单一职责原则(SRP)指出, 我们要设计职责少、理想情况下只有一个职责的组件.
399+
当应用于微服务设计架构时, 这意味着我们应努力围绕单一的业务能力或子域来设计服务.
400+
401+
402+
246403

content/posts/python-generics.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
+++
2+
date = '2025-08-14T8:00:00+08:00'
3+
draft = false
4+
title = 'Python Generics'
5+
+++
6+
本篇文件介绍 Python 中的 泛型(Generics)
7+
8+
### Intro
9+
在没有泛型的情况下, 会遇上一下几个问题:
10+
11+
1. 难以表达意图
12+
假设你编写了一个函数, 它接受一个列表, 并返回列表中的第一个元素.
13+
在不使用类型提示的情况下, 这个函数可以处理任何类型的列表, 但我们无法在函数签名中表达"返回的元素的类型与列表中的元素类型相同"这个意图
14+
```Python
15+
def get_first_element(items):
16+
return items[0]
17+
```
18+
19+
2. 丧失类型信息
20+
如果使用类型提示, 可能会像下面这样写, 但这样会丢失类型信息.
21+
`list[Any]` 表示可以接收任何类型的列表, 但 `-> Any` 意味着不知道返回的元素类型是什么, 这使得 mypy 这里静态类型检测工具无法追踪类型, 降低了代码的可读性和安全性
22+
```Python
23+
from typing import Any
24+
def get_first_element(items: list[Any]) -> Any:
25+
return items[0]
26+
27+
# 调用时, 类型检查工具无法得知 first_str 的类型
28+
first_str = get_first_element(["hello", "world"])
29+
```
30+
31+
3. 代码重复
32+
如果为每种可能的类型都编写一个单独的函数, 则会导致代码重复
33+
```Python
34+
def get_first_int(items: List[int]) -> int:
35+
return items[0]
36+
37+
def get_first_str(items: List[str]) -> str:
38+
return items[0]
39+
```
40+
41+
通过引入 **类型变量** (TypeVar) 来解决问题, 类型变量就像一个占位符, 代表在未来某时刻会被具体指定的类型
42+
```Python
43+
from typing import TypeVar
44+
45+
T = TypeVar("T")
46+
47+
def get_first_element(items: list[T]) -> T:
48+
reuturn items[0]
49+
50+
# 现在, 类型检查工具可以正确推断出类型
51+
first_str: str = get_first_element(["hello", "world"])
52+
first_int: int = get_first_element([1, 2, 3])
53+
```
54+
- `T = TypeVar('T')` 定义了一个名为 T 的类型变量, 这里 T 只是一个约定熟成的名字, 也可以使用其他字母
55+
- `items: list[T]` 表示 `items` 是一个列表, 其内部元素类型是 T
56+
- `-> T`: 返回类型也是 T
57+
- 当使用 `["hello", "world"]` 调用函数时, 静态类型检查器会推断出 T 是 str, 返回类型为 str
58+
- 当使用 `[1, 2, 3]` 调用函数时, T 被推断为 int
59+
60+
### Generic Class
61+
除了函数, 泛型也常用于定义泛型类
62+
```Python
63+
from typing import TypeVar, Generic
64+
65+
T = TypeVar("T")
66+
67+
class Box(Generic[T]):
68+
def __init__(self, items: list[T]):
69+
self._items = items
70+
71+
def get(self) -> T:
72+
return self._items[0]
73+
74+
def add(self, item: T) -> None:
75+
self._items.append(item)
76+
77+
# 创建一个存储字符串的 Box
78+
string_box = Box(["apple", "banana"])
79+
item_str = string_box.get() # str
80+
string_box.add("cherry")
81+
82+
# 创建一个存储整数的 Box
83+
int_box = Box([10, 20])
84+
item_int = int_box.get() # int
85+
int_box.add(30)
86+
```
87+
- `TypeVar` 定义类型参数: 相当于一个占位符, 将来由使用者指定具体类型
88+
- `Generic` 定义泛型类或泛型接口: 使这个类在类型检查器眼中变成一个模板
89+
90+
91+
### Advanced Useage
92+
简单介绍一下泛型的一些进阶用法
93+
94+
- 多类型参数
95+
```Python
96+
K = TypeVar("K")
97+
V = TypeVar("V")
98+
99+
class Pair(Generic[K, V]):
100+
def __init__(self, key: K, value: V):
101+
self.key = key
102+
self.value = value
103+
```
104+
支持多个类型参数, 类似 `dict[K, V]` 的结构
105+
106+
- Constraints 类型约束
107+
有时候可能希望泛型只能是某些类型
108+
```Python
109+
from typing import TypeVar
110+
111+
Number = TypeVar('Number', int, float)
112+
113+
def add(a: Number, b: Number) -> Number:
114+
return a + b
115+
```
116+
`Number` 只能为 intfloat, 传入其他类型, 类型检查工具会报错
117+
118+
- Convariant / Contravariant 协变于逆变
119+
在泛型类型中, 可以控制类型参数的变型关系
120+
```Python
121+
from typing import Generic, TypeVar
122+
123+
T_co = TypeVar("T_co", convariant=True) # 协变
124+
T_contra = TypeVar("T_contra", contravariant=True) # 逆变
125+
126+
class ReadOnlyBox(Generic[T_co]):
127+
def __init__(self, value: T_co):
128+
self.value = value
129+
130+
class Writer(Generic[T_contra]):
131+
def __init__(self, value, T_contra):
132+
...
133+
```
134+
协变: 面向产出(只读), 允许子类替代父类
135+
逆变: 面向消费(只写), 允许父类替代子类
136+
主要用于接口设计中(读/写分离)
137+
138+
- 泛型与 Protocol
139+
`Protocol` 允许定义泛型接口 (duck typing)
140+
```Python
141+
from typing import Protocol
142+
143+
class SupportsLen(Protocol):
144+
def __len__(self) -> int: ...
145+
146+
def total_length(items: list[SupportsLen]) -> int:
147+
return sum(len(x) for x in items)
148+
```
149+
任何实现了`__len__`方法的对象都能接受, 比继承更加灵活
150+
151+
- 泛型在标准库中的使用
152+
- 集和类: `list[T]`, `dict[K, V]`, `set[T]`
153+
- 迭代器: `Iterator[T]`, `Iterable[T]`
154+
- 函数工具: `Callable[[T1, T2], R]`
155+
- 上下文管理器: `ContextManager[T]`
156+
157+
```Python
158+
from typing import Callable
159+
160+
F = Callable[[int, int], int]
161+
162+
def operate(a: int, b: int, func: F) -> int:
163+
return func(a, b)
164+
```
165+
166+
167+
### Wrapping Up
168+
泛型是 Python 类型提示系统中一个非常强大的工具, 它通过类型变量帮助我们编写更加灵活、安全且可维护的代码.
169+
170+
它虽然不会影响程序的运行时行为, 但它为静态类型分析提供了必要的信息, 使得代码意图更加清晰, 并且能在早期发现类型错误.
171+
172+
Python 的泛型是类型提示系统的一部分, 和 C++/Java 的编译期泛型不同, 它的作用主要是: 帮助 IDE 和类型检查工具发现类型错误, 提升代码可读性和可维护性, 提供更精确的 API 类型签名等

public/archives/index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@
5151
<span class="max-w-[4rem] md:max-w-none truncate">Home</span></a></li><li class="flex items-center gap-1 md:gap-2 min-w-0"><span class="text-muted-foreground/50 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
5252
</span><span class="text-foreground flex items-center gap-0.5 md:gap-1 font-medium min-w-0 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
5353
<span class="max-w-[3rem] md:max-w-none truncate">Archives</span></span></li></ol></nav><header class=mb-8><div class="mb-4 flex items-center gap-3"><svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg><h1 class="text-foreground text-3xl font-bold">Archives</h1></div><p class="text-muted-foreground mb-6">Browse all articles in chronological order and discover what interests you.</p><div class="text-muted-foreground flex items-center gap-4 text-sm"><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
54-
<span>12 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55-
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">12
56-
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">12
57-
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/python-strings/ class=block>Python Strings</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
54+
<span>13 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55+
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">13
56+
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">13
57+
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/python-generics/ class=block>Python Generics</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
58+
<time datetime=2025-08-14>08-14</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
59+
<span>3
60+
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/python-strings/ class=block>Python Strings</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
5861
<time datetime=2025-08-13>08-13</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
5962
<span>2
6063
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/fastapi-response-model/ class=block>FastAPI Response Model</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>

0 commit comments

Comments
 (0)