面向对象高级编程
在掌握了面向对象基础(封装、继承、多态)之后,让我们深入学习Python提供的高级特性,包括属性装饰器、slots限制、静态方法、类方法、抽象类、元类和枚举等。
@property 装饰器
@property 允许我们将方法"伪装"成属性来访问,既保证了数据安全性,又提供了友好的API。
┌─────────────────────────────────────────────────────────────────┐
│ @property 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【传统方式】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class Person: │ │
│ │ def __init__(self, name): │ │
│ │ self.name = name # 直接暴露,无验证 │ │
│ │ │ │
│ │ p = Person("A") │ │
│ │ p.name = "" # 空的名字也可以?! │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【@property 方式】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class Person: │ │
│ │ def __init__(self, name): │ │
│ │ self.name = name # 实际上是调用 setter │ │
│ │ │ │
│ │ @property │ │
│ │ def name(self): # getter │ │
│ │ return self._name │ │
│ │ │ │
│ │ @name.setter # setter │ │
│ │ def name(self, value): │ │
│ │ if len(value) < 2: │ │
│ │ raise ValueError("名字至少2个字符") │ │
│ │ self._name = value │ │
│ │ │ │
│ │ p = Person("Alice") # OK │ │
│ │ p.name = "" # ValueError! │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
1. 基本用法
Python
class Person:
def __init__(self, name, age):
self._name = name # 私有变量,约定以 _ 开头
self._age = age
# getter - 读取属性时调用
@property
def name(self):
return self._name
# setter - 写入属性时调用
@name.setter
def name(self, value):
if len(value) < 2:
raise ValueError("名字至少2个字符")
self._name = value
# deleter - 删除属性时调用
@name.deleter
def name(self):
print(f"删除 {self._name} 的名字属性")
del self._name
p = Person("张三", 25)
# 像访问普通属性一样访问
print(p.name) # 张三 (触发 getter)
# 赋值时验证
p.name = "李四" # 触发 setter
print(p.name) # 李四
p.name = "A" # ValueError: 名字至少2个字符
2. 只读属性
只定义 @property 而不定义 setter,可以创建只读属性:
Python
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
# 只读属性:只定义 getter,不定义 setter
@property
def area(self):
return round(math.pi * self._radius ** 2, 2)
@property
def perimeter(self):
return round(2 * math.pi * self._radius, 2)
import math
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.54
print(c.perimeter) # 31.42
c.radius = 10 # AttributeError: property 'radius' has no setter
__slots__ 限制属性
__slots__ 可以限制一个类能拥有的属性,防止随意添加动态属性,并节省内存。
┌─────────────────────────────────────────────────────────────────┐
│ __slots__ 的作用 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【不使用 __slots__】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class Student: │ │
│ │ pass │ │
│ │ │ │
│ │ s = Student() │ │
│ │ s.name = "张三" # 可以任意添加属性 │ │
│ │ s.score = 100 # 可以任意添加属性 │ │
│ │ s.anything = xxx # 什么都可以添加... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【使用 __slots__】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class Student: │ │
│ │ __slots__ = ("name", "score", "grade") │ │
│ │ │ │
│ │ s = Student() │ │
│ │ s.name = "张三" # OK │ │
│ │ s.score = 100 # OK │ │
│ │ s.grade = "A" # OK │ │
│ │ s.other = xxx # AttributeError! │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【内存对比】 │
│ 普通 dict: 每个实例有独立的 __dict__,占用较多内存 │
│ __slots__: 不创建 __dict__,直接用固定大小的数组存储属性 │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
# 普通类 - 可以任意添加属性
class NormalStudent:
def __init__(self, name):
self.name = name
ns = NormalStudent("张三")
ns.score = 100 # 完全可以添加新属性
ns.anything = "任意" # 什么都可以添加
# 使用 __slots__ - 限制属性
class Student:
# 只能在 __slots__ 列表中的属性名
__slots__ = ("name", "score", "grade", "_age")
def __init__(self, name, score):
self.name = name
self.score = score
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0 or value > 150:
raise ValueError("年龄必须在 0-150 之间")
self._age = value
s = Student("李四", 95)
# 这些都是允许的
s.name = "王五" # OK
s.score = 98 # OK
s.grade = "A" # OK
s.age = 20 # OK - _age 在 slots 中
# 这些会报错
# s.other = "xxx" # AttributeError: 'Student' object has no attribute 'other'
# s.birthday = "2000-01-01" # AttributeError
# 注意: __slots__ 不会影响子类,除非子类也定义了 __slots__
# 子类如果不定义 __slots__,仍然可以有 __dict__
静态方法与类方法
Python 类中有三种方法:实例方法、静态方法和类方法。它们各有不同的用途。
┌─────────────────────────────────────────────────────────────────┐
│ 三种方法对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 实例方法 (Instance Method) │ │
│ │ - 第一个参数是 self │ │
│ │ - 可以访问和修改实例属性 │ │
│ │ - 只能通过实例调用 │ │
│ │ - 示例: def method(self, arg): │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 静态方法 (Static Method) │ │
│ │ - 不需要 self 或 cls 参数 │ │
│ │ - 与类相关但不需要访问类/实例属性 │ │
│ │ - 可以通过类或实例调用 │ │
│ │ - 示例: @staticmethod def method(arg): │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 类方法 (Class Method) │ │
│ │ - 第一个参数是 cls (类本身) │ │
│ │ - 可以访问和修改类属性 │ │
│ │ - 可以通过类或实例调用 │ │
│ │ - 示例: @classmethod def method(cls, arg): │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
1. 静态方法 @staticmethod
静态方法不需要访问类或实例的属性,纯粹是一个属于类的"函数":
Python
class MathUtil:
# 静态方法:与类和实例都无关的纯函数
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_positive(n):
return n > 0
@staticmethod
def max_of_three(a, b, c):
return max(a, b, c)
# 调用静态方法 - 可以通过类直接调用
print(MathUtil.add(3, 5)) # 8
print(MathUtil.is_positive(10)) # True
print(MathUtil.max_of_three(1, 5, 3)) # 5
# 也可以通过实例调用
m = MathUtil()
print(m.add(1, 2)) # 3
2. 类方法 @classmethod
类方法可以访问类属性,常用于创建类的实例:
Python
class Person:
total_count = 0
def __init__(self, name, age):
self.name = name
self.age = age
Person.total_count += 1
@classmethod
def from_birth_year(cls, name, birth_year):
"通过出生年份创建 Person 实例"
import datetime
age = datetime.datetime.now().year - birth_year
return cls(name, age) # 调用 __init__
@classmethod
def create_anonymous(cls):
"创建一个匿名人物"
return cls("匿名", 0)
@classmethod
def get_total_count(cls):
"获取总人数"
return cls.total_count
# 普通的创建方式
p1 = Person("张三", 25)
# 使用类方法 - 通过出生年份创建
p2 = Person.from_birth_year("李四", 1995)
print(p2.name, p2.age) # 李四 30 (假设今年2025)
# 使用类方法 - 创建匿名人物
p3 = Person.create_anonymous()
print(p3.name) # 匿名
# 使用类方法 - 获取统计信息
print(Person.get_total_count()) # 3
3. 三种方法对比
| 特性 | 实例方法 | @staticmethod | @classmethod |
|---|---|---|---|
| 第一个参数 | self (实例) | 无 | cls (类) |
| 访问实例属性 | 可以 | 不可以 | 不可以 |
| 访问类属性 | 可以 | 不可以 | 可以 |
| 通过类调用 | 不可以 | 可以 | 可以 |
| 通过实例调用 | 可以 | 可以 | 可以 |
抽象类
抽象类是一种不能直接实例化的类,它定义了一组子类必须实现的方法。使用 ABC (Abstract Base Class) 和 @abstractmethod 来创建。
┌─────────────────────────────────────────────────────────────────┐
│ 抽象类示意图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ from abc import ABC, abstractmethod │
│ │
│ class Shape(ABC): ← 抽象基类 │
│ @abstractmethod ← 抽象方法 │
│ def area(self): ... │
│ │
│ @abstractmethod │
│ def perimeter(self): ... │
│ │
│ class Circle(Shape): ← 具体类 │
│ def area(self): return πr² ← 必须实现 │
│ def perimeter(self): return 2πr ← 必须实现 │
│ │
│ shape = Shape() # TypeError! 抽象类不能实例化 │
│ circle = Circle() # OK! │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
from abc import ABC, abstractmethod
# 抽象基类
class Shape(ABC):
@abstractmethod
def area(self):
"""计算面积 - 子类必须实现"""
pass
@abstractmethod
def perimeter(self):
"""计算周长 - 子类必须实现"""
pass
# 抽象类也可以有具体方法
def info(self):
print(f"面积: {self.area()}, 周长: {self.perimeter()}")
# 具体子类
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# 抽象类不能实例化
# shape = Shape() # TypeError: Can't instantiate abstract class Shape
# 具体类可以实例化
rect = Rectangle(4, 5)
print(f"面积: {rect.area()}") # 20
print(f"周长: {rect.perimeter()}") # 18
rect.info() # 面积: 20, 周长: 18
# 强制实现检查
class IncompleteShape(Shape):
def area(self):
return 0
# 忘记实现 perimeter
# 尝试实例化会报错
# incomplete = IncompleteShape() # TypeError
元类 (Metaclass)
元类是"类的类"。普通类定义了如何创建实例,元类定义了如何创建类。
┌─────────────────────────────────────────────────────────────────┐
│ 元类示意图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【普通类】 │
│ class MyClass: ← 类 │
│ x = 1 │
│ │
│ obj = MyClass() ← 实例 │
│ │
│ 【元类】 │
│ class Meta(type): ← 元类 (type 的子类) │
│ pass │
│ │
│ class MyClass(metaclass=Meta): ← 使用元类创建类 │
│ x = 1 │
│ │
│ Python 内部: Meta("MyClass", (), {"x": 1}) │
│ │
│ 常见用途: │
│ • 自动注册类 │
│ • 验证类属性 │
│ • ORM 模型映射 │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
# 元类示例:自动注册所有子类
class Registry(type):
# 类属性:存储所有注册的类
_registry = {}
def __new__(cls, name, bases, attrs):
new_class = super().__new__(cls, name, bases, attrs)
# 将类名注册到 registry
cls._registry[name] = new_class
print(f"注册类: {name}")
return new_class
# 使用元类创建类
class Animal(metaclass=Registry):
def speak(self):
raise NotImplementedError
class Dog(Animal):
def speak(self):
return "汪汪"
class Cat(Animal):
def speak(self):
return "喵喵"
# 查看注册的类
print(f"已注册的类: {list(Registry._registry.keys())}")
# 根据名称获取类
DogClass = Registry._registry["Dog"]
dog = DogClass()
print(dog.speak()) # 汪汪
什么时候使用元类?
大多数情况下,你不需要使用元类。但如果你需要:
1. 修改类的创建过程
2. 自动注册或验证类
3. 实现 ORM 框架
那么元类就是强大的工具。
大多数情况下,你不需要使用元类。但如果你需要:
1. 修改类的创建过程
2. 自动注册或验证类
3. 实现 ORM 框架
那么元类就是强大的工具。
枚举类
枚举是一组符号名称的集合,用于表示固定的可能值。使用 enum 模块创建。
Python
from enum import Enum, IntEnum, Flag, auto
# 基本枚举
class Status(Enum):
DRAFT = 0
PUBLISHED = 1
ARCHIVED = 2
# 使用枚举
print(Status.DRAFT) # Status.DRAFT
print(Status.DRAFT.value) # 0
print(Status.DRAFT.name) # DRAFT
# 整数枚举 (可以直接比较)
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH >= Priority.MEDIUM) # True
# 自动编号枚举
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
print(list(Color)) # [<Color.RED>, <Color.GREEN>, <Color.BLUE>]
# 遍历枚举
for c in Color:
print(c.name, c.value)
知识测验
问题1:以下代码的输出是什么?
class A:
_x = 10
@property
def x(self):
return self._x
@x.setter
def x(self, v):
self._x = v * 2
a = A()
a.x = 5
print(a.x)
问题2:关于 __slots__,哪个说法是正确的?
问题3:静态方法和类方法的主要区别是什么?
课后练习
练习1:实现带验证的属性
创建一个 BankAccount 类,使用 @property 实现:
- balance 属性:只读,存款和取款通过方法操作
- 存款时验证金额为正数
- 取款时验证余额充足
- 使用 __slots__ 限制只允许有 name 和 _balance 属性
练习2:抽象类设计
设计一个支付系统:
- 抽象基类 Payment,有抽象方法 pay(amount) 和 refund()
- 子类 Alipay:实现支付宝支付
- 子类 WechatPay:实现微信支付
- 子类 UnionPay:实现银联支付
- 创建工厂类方法根据类型返回对应支付实例
练习3:枚举应用
为订单系统创建枚举:
- OrderStatus 枚举:PENDING, PAID, SHIPPED, DELIVERED, CANCELLED
- PaymentMethod 枚举:WECHAT, ALIPAY, CARD, CASH
- 创建一个 Order 类,使用这两个枚举
- 实现订单状态流转验证(不能从 PENDING 直接到 DELIVERED)
章节总结
┌─────────────────────────────────────────────────────────────────┐
│ 本章要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. @property │
│ • 将方法伪装成属性 │
│ • @property.getter - 读取 │
│ • @name.setter - 写入 │
│ • @name.deleter - 删除 │
│ • 只定义 getter 可创建只读属性 │
│ │
│ 2. __slots__ │
│ • 限制类能拥有的属性 │
│ • 可以节省内存 │
│ • 不影响子类(除非子类也定义 slots) │
│ │
│ 3. 静态方法 vs 类方法 │
│ • @staticmethod - 无需 self/cls,纯粹函数 │
│ • @classmethod - 第一个参数是 cls,可访问类属性 │
│ │
│ 4. 抽象类 │
│ • from abc import ABC, abstractmethod │
│ • 不能直接实例化 │
│ • 子类必须实现所有抽象方法 │
│ │
│ 5. 元类 │
│ • 类的类 │
│ • 用于控制类的创建过程 │
│ • 用途:自动注册、验证、ORM等 │
│ │
│ 6. 枚举 │
│ • 固定的可能值集合 │
│ • IntEnum 支持数值比较 │
│ • auto() 自动分配编号 │
│ │
└─────────────────────────────────────────────────────────────────┘
🐍 Python 在线代码编辑器
📝 运行结果