Python 的几个重要的相关概念
1. EAFP vs LBYL
这是 Python 的核心编程风格,与鸭子类型密切相关。
EAFP (Easier to Ask for Forgiveness than Permission)
"请求原谅比获得许可更容易" - 直接尝试操作,如果出错就处理异常。
# EAFP 风格 - Pythonic
def get_length(obj):try:return len(obj)except TypeError:return "对象没有长度"print(get_length([1, 2, 3])) # 3
print(get_length(42)) # 对象没有长度LBYL (Look Before You Leap)
"三思而后行" - 先检查再操作。
# LBYL 风格
def get_length(obj):if hasattr(obj, '__len__'):return len(obj)else:return "对象没有长度"EAFP 与鸭子类型的关系:都体现了"先尝试,有问题再处理"的 Python 哲学。
2. 协议
协议是鸭子类型的正式化表达,通过 typing.Protocol 实现。
from typing import Protocol, runtime_checkable@runtime_checkable
class SupportsAdd(Protocol):def __add__(self, other): ...class Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)class StringWrapper:def __init__(self, value):self.value = valuedef __add__(self, other):return StringWrapper(self.value + other.value)def add_objects(a, b: SupportsAdd) -> SupportsAdd:"""接受任何实现了 __add__ 的对象"""return a + b# 使用
v1 = Vector(1, 2)
v2 = Vector(3, 4)
result = add_objects(v1, v2) # 正常工作s1 = StringWrapper("hello")
s2 = StringWrapper(" world")
result = add_objects(s1, s2) # 也正常工作print(isinstance(v1, SupportsAdd)) # True - 运行时检查
print(isinstance("hello", SupportsAdd)) # False - 字符串没有 __add__ 方法3. 结构化类型
关注对象的结构(有哪些方法/属性)而非继承关系。
from typing import TypedDictclass PersonDetails(TypedDict):name: strage: intdef process_person(data: PersonDetails) -> str:return f"{data['name']} is {data['age']} years old"# 任何具有 'name' 和 'age' 键的字典都可以使用
person1 = {'name': 'Alice', 'age': 25}
person2 = {'name': 'Bob', 'age': 30, 'city': 'NY'} # 额外的属性也可以print(process_person(person1)) # Alice is 25 years old
print(process_person(person2)) # Bob is 30 years old4. 猴子补丁
在运行时动态修改类或模块。
class Person:def __init__(self, name):self.name = namedef greet(self):return f"Hello, I'm {self.name}"# 猴子补丁:运行时添加新方法
def introduce(self):return f"My name is {self.name}"Person.introduce = introduceperson = Person("Alice")
print(person.greet()) # Hello, I'm Alice
print(person.introduce()) # My name is Alice - 运行时添加的方法5. 混入类
通过多重继承提供可重用的功能。
class JSONSerializableMixin:def to_json(self):import jsonreturn json.dumps(self.__dict__)class XMLSerializableMixin:def to_xml(self):attrs = ''.join(f' {k}="{v}"' for k, v in self.__dict__.items())return f"<object{attrs}/>"class Person(JSONSerializableMixin, XMLSerializableMixin):def __init__(self, name, age):self.name = nameself.age = ageclass Product(JSONSerializableMixin):def __init__(self, name, price):self.name = nameself.price = price# 使用
person = Person("Alice", 25)
print(person.to_json()) # {"name": "Alice", "age": 25}
print(person.to_xml()) # <object name="Alice" age="25"/>product = Product("Book", 29.99)
print(product.to_json()) # {"name": "Book", "price": 29.99}
# product.to_xml() # 会报错,因为没有混入 XMLSerializableMixin6. 抽象基类
虽然与鸭子类型相反(强调显式继承),但 ABC 可以用来定义"协议"。
from abc import ABC, abstractmethod
from collections.abc import Sized, Iterableclass Drawable(ABC):@abstractmethoddef draw(self):passclass Circle(Drawable):def draw(self):return "绘制圆形"class Square(Drawable):def draw(self):return "绘制正方形"def render_all(drawables):for obj in drawables:if isinstance(obj, Drawable): # 显式检查print(obj.draw())# 使用内置的 ABC
def process_sized_objects(obj: Sized):"""接受任何有 __len__ 的对象"""return f"长度: {len(obj)}"print(process_sized_objects([1, 2, 3])) # 长度: 3
print(process_sized_objects({"a": 1}))) # 长度: 17. 上下文管理器协议
任何实现了 __enter__ 和 __exit__ 方法的对象都可以用于 with 语句。
class Timer:def __init__(self, name):self.name = namedef __enter__(self):import timeself.start = time.time()print(f"开始 {self.name}")return selfdef __exit__(self, exc_type, exc_val, exc_tb):import timeelapsed = time.time() - self.startprint(f"结束 {self.name}, 耗时: {elapsed:.2f}秒")# 使用
with Timer("计算任务"):sum(range(1000000))# 输出:
# 开始 计算任务
# 结束 计算任务, 耗时: 0.05秒8. 可调用对象协议
任何实现了 __call__ 方法的对象都可以像函数一样调用。
class Multiplier:def __init__(self, factor):self.factor = factordef __call__(self, x):return x * self.factorclass Greeter:def __call__(self, name):return f"Hello, {name}!"# 使用
double = Multiplier(2)
greet = Greeter()print(double(5)) # 10 - 像函数一样调用
print(greet("Alice")) # Hello, Alice! - 像函数一样调用# 可以传递给期望函数的参数
numbers = [1, 2, 3, 4]
doubled = list(map(double, numbers))
print(doubled) # [2, 4, 6, 8]9. 描述符协议
控制属性访问的行为。
class ValidatedString:def __init__(self, min_length=0, max_length=100):self.min_length = min_lengthself.max_length = max_lengthdef __set_name__(self, owner, name):self.name = namedef __get__(self, obj, objtype=None):return obj.__dict__.get(self.name)def __set__(self, obj, value):if not isinstance(value, str):raise TypeError("必须是字符串")if not (self.min_length <= len(value) <= self.max_length):raise ValueError(f"长度必须在 {self.min_length} 到 {self.max_length} 之间")obj.__dict__[self.name] = valueclass Person:name = ValidatedString(1, 50)email = ValidatedString(5, 100)def __init__(self, name, email):self.name = nameself.email = email# 使用
person = Person("Alice", "alice@example.com")
print(person.name) # Alicetry:person.name = "" # 会抛出 ValueError
except ValueError as e:print(f"错误: {e}")总结
这些概念都与鸭子类型共享相同的精神:
| 概念 | 核心思想 | 与鸭子类型的关系 |
|---|---|---|
| EAFP | 先尝试,出错再处理 | 编程风格的体现 |
| 协议 | 正式化的鸭子类型 | 类型系统的支持 |
| 结构化类型 | 关注结构而非名称 | 数据层面的鸭子类型 |
| 猴子补丁 | 运行时修改行为 | 动态性的极端体现 |
| 混入类 | 通过组合提供功能 | 代码复用的方式 |
| 上下文管理器 | 通过方法实现协议 | 特定协议的鸭子类型 |
| 可调用对象 | 像函数一样使用对象 | 调用协议的鸭子类型 |
| 描述符 | 控制属性访问 | 属性访问协议的鸭子类型 |
这些概念共同构成了 Python 的"动态类型系统",让 Python 代码既灵活又强大,鼓励基于行为而非类型的编程风格。
