类型系统练习答案
type-hints-basics
练习 1:为函数添加类型注解
要求:为以下函数添加完整的类型注解。
python
def calculate_discount(price, discount_percent):
discount = price * (discount_percent / 100)
return price - discount答案:
python
def calculate_discount(price: float, discount_percent: float) -> float:
"""计算折扣后的价格"""
discount = price * (discount_percent / 100)
return price - discount
# 测试
print(calculate_discount(100.0, 20.0)) # 80.0
print(calculate_discount(50.0, 10.0)) # 45.0关键点:
price和discount_percent使用float而非int,支持小数- 返回值也是
float - 类型注解不影响运行时行为
练习 2:可选返回值
要求:实现一个查找函数,找不到时返回 None。
python
def find_item(items: list[str], target: str) -> ???:
for item in items:
if item == target:
return item
return None答案:
python
from typing import Optional
def find_item(items: list[str], target: str) -> Optional[str]:
"""查找列表中的项,找不到返回 None"""
for item in items:
if item == target:
return item
return None
# 或使用 Python 3.10+ 语法
def find_item(items: list[str], target: str) -> str | None:
for item in items:
if item == target:
return item
return None
# 测试
items = ["apple", "banana", "cherry"]
result = find_item(items, "banana")
if result is not None:
print(result.upper()) # BANANA
result = find_item(items, "grape")
print(result) # None关键点:
Optional[str]等价于str | None- 使用
if result is not None进行类型守卫 - 守卫后,类型检查器知道
result是str
练习 3:联合类型
要求:实现一个函数,接受整数或字符串,返回其长度或值的两倍。
答案:
python
def process_value(value: int | str) -> int:
"""处理整数或字符串"""
if isinstance(value, int):
return value * 2
else:
return len(value)
# 测试
print(process_value(5)) # 10
print(process_value("hello")) # 5关键点:
- 使用
isinstance进行类型守卫 - 每个分支的类型明确
generic-types
练习 1:泛型函数
要求:实现一个泛型函数 last,返回列表的最后一个元素。
答案:
python
from typing import TypeVar
T = TypeVar('T')
def last(items: list[T]) -> T:
"""返回列表的最后一个元素"""
if not items:
raise ValueError("List is empty")
return items[-1]
# 测试
numbers = [1, 2, 3, 4, 5]
last_number = last(numbers) # last_number: int
print(last_number) # 5
names = ["Alice", "Bob", "Charlie"]
last_name = last(names) # last_name: str
print(last_name) # Charlie关键点:
TypeVar定义类型变量T- 输入和输出类型保持一致
- 类型检查器自动推断具体类型
练习 2:泛型类
要求:实现一个泛型 Box 类,可以存储任意类型的值。
答案:
python
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
"""泛型容器"""
def __init__(self, value: T) -> None:
self._value = value
def get(self) -> T:
return self._value
def set(self, value: T) -> None:
self._value = value
# 测试
int_box: Box[int] = Box(42)
print(int_box.get()) # 42
int_box.set(100)
print(int_box.get()) # 100
str_box: Box[str] = Box("hello")
print(str_box.get()) # hello
# str_box.set(42) # 类型错误关键点:
- 继承
Generic[T]创建泛型类 - 所有方法使用同一个
T - 实例化时指定具体类型
练习 3:类型约束
要求:实现一个函数,只接受数字类型(int 或 float)。
答案:
python
from typing import TypeVar
Number = TypeVar('Number', int, float)
def square(value: Number) -> Number:
"""计算平方"""
return value * value # type: ignore
# 测试
print(square(5)) # 25 (int)
print(square(3.14)) # 9.8596 (float)
# print(square("5")) # 类型错误关键点:
TypeVar('Number', int, float)限制只能是int或float- 返回类型与输入类型一致
protocols
练习 1:定义 Protocol
要求:定义一个 Serializable Protocol,要求有 to_dict 方法。
答案:
python
from typing import Protocol
class Serializable(Protocol):
def to_dict(self) -> dict:
"""转换为字典"""
...
class User:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def to_dict(self) -> dict:
return {"name": self.name, "age": self.age}
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def to_dict(self) -> dict:
return {"name": self.name, "price": self.price}
def save_to_json(obj: Serializable) -> str:
"""保存为 JSON"""
import json
return json.dumps(obj.to_dict())
# 测试
user = User("Alice", 30)
product = Product("Book", 29.99)
print(save_to_json(user)) # {"name": "Alice", "age": 30}
print(save_to_json(product)) # {"name": "Book", "price": 29.99}关键点:
- Protocol 定义接口契约
- 无需显式继承
- 只要有
to_dict方法就满足协议
练习 2:泛型 Protocol
要求:定义一个泛型 Container Protocol,支持添加和获取元素。
答案:
python
from typing import Protocol, TypeVar
T = TypeVar('T')
class Container(Protocol[T]):
def add(self, item: T) -> None:
...
def get_all(self) -> list[T]:
...
class IntList:
def __init__(self) -> None:
self._items: list[int] = []
def add(self, item: int) -> None:
self._items.append(item)
def get_all(self) -> list[int]:
return self._items.copy()
def fill_container(container: Container[int], items: list[int]) -> None:
for item in items:
container.add(item)
# 测试
int_list = IntList()
fill_container(int_list, [1, 2, 3, 4, 5])
print(int_list.get_all()) # [1, 2, 3, 4, 5]关键点:
Protocol[T]定义泛型协议- 方法签名使用
T - 类型检查器验证兼容性
advanced-types
练习 1:Literal 类型
要求:实现一个配置函数,只接受特定的环境名称。
答案:
python
from typing import Literal
Environment = Literal["development", "staging", "production"]
class Config:
def __init__(self, env: Environment) -> None:
self.env = env
self.debug = env == "development"
self.api_url = self._get_api_url()
def _get_api_url(self) -> str:
if self.env == "development":
return "http://localhost:8000"
elif self.env == "staging":
return "https://staging.api.example.com"
else:
return "https://api.example.com"
# 测试
dev_config = Config("development")
print(dev_config.api_url) # http://localhost:8000
prod_config = Config("production")
print(prod_config.api_url) # https://api.example.com
# Config("test") # 类型错误关键点:
Literal限制值为特定字面量- 适合配置、状态枚举
练习 2:TypedDict
要求:定义一个 API 响应的类型,包含必需和可选字段。
答案:
python
from typing import TypedDict, NotRequired
class ApiResponse(TypedDict):
success: bool
data: dict
message: NotRequired[str] # 可选字段
error: NotRequired[str] # 可选字段
def handle_response(response: ApiResponse) -> None:
if response["success"]:
print(f"Data: {response['data']}")
if "message" in response:
print(f"Message: {response['message']}")
else:
print(f"Error: {response.get('error', 'Unknown error')}")
# 测试
success_response: ApiResponse = {
"success": True,
"data": {"user_id": 123},
"message": "User created"
}
error_response: ApiResponse = {
"success": False,
"data": {},
"error": "Invalid credentials"
}
handle_response(success_response)
handle_response(error_response)关键点:
NotRequired标记可选字段(Python 3.11+)- TypedDict 提供字典结构验证
- 适合 API 响应、配置对象
练习 3:overload
要求:实现一个函数,根据参数类型返回不同类型。
答案:
python
from typing import overload
@overload
def to_string(value: int) -> str:
...
@overload
def to_string(value: list[int]) -> list[str]:
...
def to_string(value: int | list[int]) -> str | list[str]:
"""将整数或整数列表转换为字符串"""
if isinstance(value, int):
return str(value)
else:
return [str(v) for v in value]
# 测试
result1 = to_string(42) # result1: str
print(result1) # "42"
result2 = to_string([1, 2, 3]) # result2: list[str]
print(result2) # ["1", "2", "3"]关键点:
@overload定义多个类型签名- 最后一个函数是实际实现
- 类型检查器根据参数类型选择签名
总结:这些练习覆盖了 Python 类型系统的核心概念,从基础类型注解到高级泛型和协议。掌握这些技能可以编写更安全、可维护的代码。