第 11 章 面向对象编程基础
11.1 类与实例
class Dog:
# 类属性 — 所有实例共享
species = "Canis familiaris"
# 初始化方法(构造方法)
def __init__(self, name, age):
# 实例属性 — 每个实例独有
self.name = name
self.age = age
# 实例方法
def bark(self):
return f"{self.name} says: Woof!"
def info(self):
return f"{self.name}, {self.age} years old"
# 创建实例
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print(dog1.bark()) # Buddy says: Woof!
print(dog2.info()) # Max, 5 years old
print(dog1.species) # Canis familiaris
print(Dog.species) # Canis familiaris
print(type(dog1)) # <class '__main__.Dog'>
print(isinstance(dog1, Dog)) # True
self 的本质
self 就是实例本身,不是关键字,只是约定俗成的名字:
class MyClass:
def method(self):
print(f"self is {self}")
obj = MyClass()
obj.method() # self is <__main__.MyClass object at 0x...>
# obj.method() 等价于 MyClass.method(obj)
MyClass.method(obj) # 完全等价
11.2 __init__ 构造方法
__init__ 在实例创建后被调用,用于初始化属性:
class Circle:
def __init__(self, radius):
if radius < 0:
raise ValueError("半径不能为负")
self.radius = radius
self.area = 3.14159 * radius ** 2 # 初始化时计算
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.53975
注意:__init__ 不是真正的构造方法。真正创建对象的是 __new__,__init__ 只是初始化:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
a = Singleton(1)
b = Singleton(2)
print(a is b) # True — 同一个实例
print(a.value) # 2 — 被第二次 __init__ 覆盖
11.3 实例属性与类属性
class Student:
# 类属性 — 定义在类体中
school = "Python University"
count = 0
def __init__(self, name):
# 实例属性 — 定义在 __init__ 中
self.name = name
Student.count += 1 # 通过类名访问类属性
s1 = Student("Alice")
s2 = Student("Bob")
# 类属性通过类或实例都能访问
print(Student.school) # Python University
print(s1.school) # Python University
print(Student.count) # 2
# 通过实例"修改"类属性实际上是创建了同名的实例属性
s1.school = "MIT"
print(s1.school) # MIT — 实例属性(遮蔽了类属性)
print(s2.school) # Python University — 类属性不变
print(Student.school) # Python University — 类属性不变
可变类属性的陷阱:
class BadExample:
shared_list = [] # 所有实例共享同一个列表!
def add(self, item):
self.shared_list.append(item)
a = BadExample()
b = BadExample()
a.add(1)
print(b.shared_list) # [1] — b 也受影响!
class GoodExample:
def __init__(self):
self.my_list = [] # 每个实例独有
11.4 实例方法、类方法、静态方法
class Pizza:
def __init__(self, radius, ingredients):
self.radius = radius
self.ingredients = ingredients
# 实例方法 — 通过 self 访问实例
def area(self):
return 3.14159 * self.radius ** 2
# 类方法 — 通过 cls 访问类,常用作替代构造方法
@classmethod
def margherita(cls):
return cls(30, ["mozzarella", "tomatoes"])
@classmethod
def prosciutto(cls):
return cls(30, ["mozzarella", "tomatoes", "ham"])
# 静态方法 — 不访问实例也不访问类
@staticmethod
def validate_radius(radius):
return radius > 0
# 实例方法
p = Pizza(25, ["cheese"])
print(p.area())
# 类方法作为工厂方法
m = Pizza.margherita()
print(m.ingredients) # ['mozzarella', 'tomatoes']
# 静态方法
print(Pizza.validate_radius(10)) # True
print(Pizza.validate_radius(-1)) # False
三者对比
| 实例方法 | 类方法 | 静态方法 | |
|---|---|---|---|
| 装饰器 | 无 | @classmethod | @staticmethod |
| 第一个参数 | self(实例) | cls(类) | 无 |
| 访问实例属性 | 可以 | 不可以 | 不可以 |
| 访问类属性 | 可以 | 可以 | 不可以(除非硬编码类名) |
| 常见用途 | 操作实例 | 工厂方法 | 工具函数 |
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"Date({self.year}, {self.month}, {self.day})"
# 类方法:从字符串创建(工厂方法)
@classmethod
def from_string(cls, date_str):
year, month, day = map(int, date_str.split("-"))
return cls(year, month, day)
@classmethod
def today(cls):
import datetime
t = datetime.date.today()
return cls(t.year, t.month, t.day)
# 静态方法:验证日期是否合法
@staticmethod
def is_valid(year, month, day):
return 1 <= month <= 12 and 1 <= day <= 31
d1 = Date(2026, 3, 31)
d2 = Date.from_string("2026-03-31")
d3 = Date.today()
print(Date.is_valid(2026, 13, 1)) # False
11.5 属性访问控制:公有、保护、私有
Python 没有真正的访问控制,靠命名约定:
class Account:
def __init__(self, owner, balance):
self.owner = owner # 公有 — 任何人都能访问
self._balance = balance # 保护 — 约定"请勿直接访问"
self.__secret = "123" # 私有 — 名称改写(name mangling)
def get_balance(self):
return self._balance
acc = Account("Alice", 1000)
# 公有 — 直接访问
print(acc.owner) # Alice
# 保护 — 可以访问,但约定不应该
print(acc._balance) # 1000(能访问,但 IDE 可能警告)
# 私有 — 名称改写
# print(acc.__secret) # AttributeError!
print(acc._Account__secret) # "123" — 通过改写后的名字仍然能访问
名称改写(Name Mangling)的目的不是安全,而是避免子类意外覆盖:
class Parent:
def __init__(self):
self.__x = 10 # 改写为 _Parent__x
class Child(Parent):
def __init__(self):
super().__init__()
self.__x = 20 # 改写为 _Child__x — 不会冲突!
c = Child()
print(c._Parent__x) # 10
print(c._Child__x) # 20
最佳实践:
- 默认用公有属性,Python 社区信任开发者
- 用单下划线
_表示内部实现细节 - 只有在需要避免子类命名冲突时才用双下划线
__ - 提供公开接口时用
@property
11.6 @property 描述符快捷方式
@property 让方法看起来像属性,实现受控访问:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # 存储内部值
@property
def celsius(self):
"""获取摄氏温度"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""设置摄氏温度(带验证)"""
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
@celsius.deleter
def celsius(self):
"""删除温度值"""
print("删除温度")
del self._celsius
@property
def fahrenheit(self):
"""自动计算华氏温度(只读属性)"""
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.celsius) # 25 — 像访问属性一样
print(t.fahrenheit) # 77.0 — 自动计算
t.celsius = 30 # 像赋值一样,但会执行 setter
# t.celsius = -300 # ValueError: 温度不能低于绝对零度
# t.fahrenheit = 100 # AttributeError: 没有 setter,只读
del t.celsius # 删除温度
用 property 实现渐进式重构
# 阶段 1:简单属性
class User:
def __init__(self, name):
self.name = name
# 阶段 2:需要加验证了,但不想改调用方式
class User:
def __init__(self, name):
self.name = name # 这里会触发 setter
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value.strip():
raise ValueError("名字不能为空")
self._name = value.strip()
# 调用方式完全不变
user = User("Alice")
print(user.name) # Alice
user.name = "Bob" # 有验证
# user.name = " " # ValueError
缓存属性
class DataProcessor:
def __init__(self, data):
self._data = data
self._result = None
@property
def result(self):
if self._result is None:
print("计算中...(耗时操作)")
self._result = sum(x ** 2 for x in self._data)
return self._result
dp = DataProcessor(range(1000))
print(dp.result) # 计算中... 332833500
print(dp.result) # 332833500(直接返回缓存)
# Python 3.8+ 可以用 functools.cached_property
from functools import cached_property
class DataProcessor:
def __init__(self, data):
self.data = data
@cached_property
def result(self):
print("计算中...")
return sum(x ** 2 for x in self.data)
本章小结:Python 的 OOP 简洁而实用。核心要点:理解
self的本质、类属性与实例属性的区别、三种方法类型的适用场景、名称改写的真正用途、以及@property实现受控访问。Python 的 OOP 不追求严格封装,而是建立在信任和约定之上。