Skip to content

类型系统练习答案 ​

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 类型系统的核心概念,从基础类型注解到高级泛型和协议。掌握这些技能可以编写更安全、可维护的代码。

用心记录每一步成长