面向对象编程#
面向对象编程(OOP)是一种编程范式,使用类和对象来组织代码。Python 完全支持面向对象编程。
类和对象基础#
定义类#
# 基本语法
class ClassName:
"""类的文档字符串"""
# 类体
# 示例
class Dog:
"""狗类"""
pass
# 创建对象(实例化)
my_dog = Dog()
print(type(my_dog)) # <class '__main__.Dog'>实例属性#
class Dog:
def __init__(self, name, age):
"""初始化方法"""
self.name = name # 实例属性
self.age = age
# 创建对象
my_dog = Dog("旺财", 3)
print(my_dog.name) # 旺财
print(my_dog.age) # 3
# 动态添加属性
my_dog.color = "棕色"
print(my_dog.color) # 棕色实例方法#
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
"""实例方法"""
print(f"{self.name}在叫:汪汪!")
def get_info(self):
return f"{self.name}今年{self.age}岁"
my_dog = Dog("旺财", 3)
my_dog.bark() # 旺财在叫:汪汪!
print(my_dog.get_info()) # 旺财今年3岁类属性和实例属性#
类属性#
类属性属于类本身,所有实例共享。
class Dog:
# 类属性
species = "犬科"
count = 0
def __init__(self, name):
self.name = name
Dog.count += 1 # 访问类属性
dog1 = Dog("旺财")
dog2 = Dog("小黑")
print(dog1.species) # 犬科
print(dog2.species) # 犬科
print(Dog.count) # 2(所有实例共享)实例属性 vs 类属性#
class Dog:
species = "犬科" # 类属性
def __init__(self, name):
self.name = name # 实例属性
dog1 = Dog("旺财")
dog2 = Dog("小黑")
# 修改类属性会影响所有实例
Dog.species = "哺乳动物"
print(dog1.species) # 哺乳动物
print(dog2.species) # 哺乳动物
# 通过实例修改类属性(实际上是创建了实例属性)
dog1.species = "犬科"
print(dog1.species) # 犬科(实例属性)
print(dog2.species) # 哺乳动物(类属性)
print(Dog.species) # 哺乳动物(类属性未改变)方法类型(重点难点)#
Python 中有三种方法类型:实例方法、类方法和静态方法。理解它们的区别和使用场景是掌握面向对象编程的关键。
实例方法(Instance Method)#
实例方法是最常见的方法类型,第一个参数必须是 self(指向实例本身)。实例方法可以访问和修改实例属性和类属性。
特点:
- 第一个参数是
self(约定名称,可以是其他名称但不推荐) - 可以访问实例属性(通过
self) - 可以访问类属性(通过
self或类名) - 必须通过实例调用(虽然可以通过类调用,但不推荐)
class Dog:
species = "犬科" # 类属性
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age
def bark(self): # 实例方法
"""访问实例属性"""
print(f"{self.name}在叫:汪汪!")
def get_info(self):
"""访问实例属性和类属性"""
return f"{self.name}是{self.species},今年{self.age}岁"
def have_birthday(self):
"""修改实例属性"""
self.age += 1
print(f"{self.name}过生日了,现在{self.age}岁")
my_dog = Dog("旺财", 3)
my_dog.bark() # 旺财在叫:汪汪!
print(my_dog.get_info()) # 旺财是犬科,今年3岁
my_dog.have_birthday() # 旺财过生日了,现在4岁
# 通过类调用实例方法(不推荐,需要手动传入实例)
Dog.bark(my_dog) # 旺财在叫:汪汪!调用方式:
- 推荐:
instance.method()- Python 自动将实例作为self传入 - 不推荐:
Class.method(instance)- 需要手动传入实例
类方法(Class Method)#
类方法使用 @classmethod 装饰器,第一个参数必须是 cls(指向类本身)。类方法可以访问和修改类属性,但不能直接访问实例属性。
特点:
- 使用
@classmethod装饰器 - 第一个参数是
cls(约定名称,指向类本身) - 可以访问类属性(通过
cls) - 不能直接访问实例属性(因为没有
self) - 可以通过类或实例调用(推荐通过类调用)
- 常用于替代构造函数或操作类级别的数据
class Dog:
count = 0 # 类属性:记录创建的实例数量
all_dogs = [] # 类属性:存储所有实例
def __init__(self, name, age):
self.name = name
self.age = age
Dog.count += 1
Dog.all_dogs.append(self)
@classmethod
def get_count(cls):
"""获取创建的实例数量"""
return cls.count
@classmethod
def create_from_string(cls, string):
"""类方法:替代构造函数(工厂方法)"""
# 从字符串解析数据并创建实例
parts = string.split()
name = parts[0]
age = int(parts[1]) if len(parts) > 1 else 0
return cls(name, age) # 使用 cls 而不是硬编码类名,支持继承
@classmethod
def create_puppy(cls, name):
"""类方法:创建特定类型的实例"""
return cls(name, 0) # 幼犬年龄为0
@classmethod
def get_all_names(cls):
"""获取所有实例的名称"""
return [dog.name for dog in cls.all_dogs]
# 使用类方法
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)
print(Dog.get_count()) # 2(通过类调用)
print(dog1.get_count()) # 2(通过实例调用也可以,但不推荐)
# 使用类方法作为替代构造函数
dog3 = Dog.create_from_string("小白 2")
print(dog3.name, dog3.age) # 小白 2
dog4 = Dog.create_puppy("小黄")
print(dog4.name, dog4.age) # 小黄 0
print(Dog.get_all_names()) # ['旺财', '小黑', '小白', '小黄']类方法在继承中的优势:
class Animal:
@classmethod
def create(cls, name):
"""使用 cls 而不是硬编码类名"""
return cls(name) # 如果子类调用,会创建子类实例
class Dog(Animal):
def __init__(self, name):
self.name = name
print(f"创建了狗:{name}")
class Cat(Animal):
def __init__(self, name):
self.name = name
print(f"创建了猫:{name}")
# 使用类方法,会根据调用的类创建对应类型的实例
dog = Dog.create("旺财") # 创建了狗:旺财
cat = Cat.create("小花") # 创建了猫:小花静态方法(Static Method)#
静态方法使用 @staticmethod 装饰器,不需要 self 或 cls 参数。静态方法与类相关,但不依赖类或实例的状态。
特点:
- 使用
@staticmethod装饰器 - 不需要
self或cls参数 - 不能直接访问类属性或实例属性
- 可以通过类或实例调用
- 常用于工具函数,逻辑上与类相关但不需要访问类或实例数据
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def is_valid_name(name):
"""验证名称是否有效(与类相关但不依赖实例)"""
return isinstance(name, str) and len(name) > 0 and name.isalpha()
@staticmethod
def calculate_dog_years(human_years):
"""将狗年龄转换为人类年龄"""
if human_years <= 2:
return human_years * 10.5
else:
return 21 + (human_years - 2) * 4
@staticmethod
def format_dog_info(name, age):
"""格式化狗的信息"""
return f"名称:{name},年龄:{age}岁"
# 通过类调用(推荐)
print(Dog.is_valid_name("旺财")) # True
print(Dog.is_valid_name("123")) # False
print(Dog.calculate_dog_years(3)) # 25.0
print(Dog.format_dog_info("旺财", 3)) # 名称:旺财,年龄:3岁
# 通过实例调用(也可以,但不推荐)
my_dog = Dog("旺财", 3)
print(my_dog.is_valid_name("小黑")) # True方法绑定机制#
理解 Python 的方法绑定机制有助于更好地理解三种方法类型:
class MyClass:
class_attr = "类属性"
def __init__(self):
self.instance_attr = "实例属性"
def instance_method(self):
return f"实例方法:{self.instance_attr}"
@classmethod
def class_method(cls):
return f"类方法:{cls.class_attr}"
@staticmethod
def static_method():
return "静态方法"
obj = MyClass()
# 方法绑定
print(type(obj.instance_method)) # <class 'method'>(绑定方法)
print(type(MyClass.instance_method)) # <class 'function'>(未绑定函数)
print(type(obj.class_method)) # <class 'method'>(绑定方法)
print(type(MyClass.class_method)) # <class 'method'>(绑定方法)
print(type(obj.static_method)) # <class 'function'>(普通函数)
print(type(MyClass.static_method)) # <class 'function'>(普通函数)
# 调用方式
print(obj.instance_method()) # 自动传入 self
print(MyClass.instance_method(obj)) # 手动传入实例
print(obj.class_method()) # 自动传入 cls(实际是 MyClass)
print(MyClass.class_method()) # 自动传入 cls(MyClass)
print(obj.static_method()) # 不传入任何参数
print(MyClass.static_method()) # 不传入任何参数选择指南#
何时使用实例方法:
- 需要访问或修改实例属性
- 操作特定实例的数据
- 大多数常规方法都是实例方法
何时使用类方法:
- 需要访问或修改类属性
- 创建替代构造函数(工厂方法)
- 需要根据子类动态创建实例(使用
cls而不是硬编码类名) - 操作类级别的数据(如计数器、注册表等)
何时使用静态方法:
- 方法与类相关,但不依赖类或实例的状态
- 工具函数,逻辑上属于类但不需要访问类数据
- 可以独立于类存在的函数,但放在类中更有组织性
- 避免创建不必要的实例来调用方法
常见错误和注意事项#
class Example:
class_attr = "类属性"
def __init__(self):
self.instance_attr = "实例属性"
def instance_method(self):
# ✅ 正确:访问实例属性
print(self.instance_attr)
# ✅ 正确:访问类属性
print(self.class_attr)
@classmethod
def class_method(cls):
# ❌ 错误:不能访问实例属性(没有 self)
# print(cls.instance_attr) # AttributeError
# ✅ 正确:访问类属性
print(cls.class_attr)
# ⚠️ 注意:可以通过硬编码类名访问,但不推荐
print(Example.class_attr) # 可以,但不推荐
@staticmethod
def static_method():
# ❌ 错误:不能访问实例属性
# print(self.instance_attr) # NameError: name 'self' is not defined
# ⚠️ 技术上可以访问类属性,但不推荐(破坏了封装性)
# print(Example.class_attr) # 可以,但不推荐
# ✅ 正确:不访问任何类或实例数据
print("这是静态方法")特殊方法(魔术方法)#
特殊方法以双下划线开头和结尾,用于定义类的行为。
__init__ 和 __str__#
class Dog:
def __init__(self, name, age):
"""初始化方法"""
self.name = name
self.age = age
def __str__(self):
"""字符串表示(用户友好)"""
return f"Dog(name={self.name}, age={self.age})"
def __repr__(self):
"""对象表示(开发者友好)"""
return f"Dog('{self.name}', {self.age})"
my_dog = Dog("旺财", 3)
print(my_dog) # Dog(name=旺财, age=3)(调用__str__)
print(repr(my_dog)) # Dog('旺财', 3)(调用__repr__)比较运算符#
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
"""等于 =="""
return self.age == other.age
def __lt__(self, other):
"""小于 <"""
return self.age < other.age
def __le__(self, other):
"""小于等于 <="""
return self.age <= other.age
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)
print(dog1 == dog2) # False
print(dog1 < dog2) # True算术运算符#
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""加法 +"""
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""减法 -"""
return Point(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""乘法 *"""
return Point(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3) # Point(4, 6)其他常用特殊方法#
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
"""len()函数"""
return len(self.items)
def __getitem__(self, index):
"""索引访问"""
return self.items[index]
def __setitem__(self, index, value):
"""索引赋值"""
self.items[index] = value
def __contains__(self, item):
"""in运算符"""
return item in self.items
def __call__(self):
"""使对象可调用"""
return "对象被调用了"
my_list = MyList([1, 2, 3])
print(len(my_list)) # 3
print(my_list[0]) # 1
my_list[0] = 10
print(2 in my_list) # True
print(my_list()) # 对象被调用了继承(重点难点)#
继承允许一个类继承另一个类的属性和方法。
所有类都继承自 object,而 object 提供了一些基础的特殊方法(如 __init__、__str__、__repr__ 等),所以即使不显式继承,也能使用这些基础方法
继承特殊方法的规则#
子类会继承父类的所有方法,包括特殊方法(魔术方法)
如果子类没有重写特殊方法,就会使用父类的版本
如果子类重写了特殊方法,就会使用子类的版本
基本继承#
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "动物在叫"
class Dog(Animal):
def speak(self):
"""重写父类方法"""
return f"{self.name}在叫:汪汪!"
class Cat(Animal):
def speak(self):
"""重写父类方法"""
return f"{self.name}在叫:喵喵!"
dog = Dog("旺财")
cat = Cat("小花")
print(dog.speak()) # 旺财在叫:汪汪!
print(cat.speak()) # 小花在叫:喵喵!super() 函数#
super() 用于调用父类的方法。
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的__init__
self.breed = breed
dog = Dog("旺财", "金毛")
print(dog.name) # 旺财
print(dog.breed) # 金毛多重继承#
Python 支持多重继承。
class Flyable:
def fly(self):
return "飞"
class Swimmable:
def swim(self):
return "游"
class Duck(Flyable, Swimmable):
pass
duck = Duck()
print(duck.fly()) # 飞
print(duck.swim()) # 游方法解析顺序(MRO)#
MRO 决定了在多重继承中方法查找的顺序。
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
d = D()
print(d.method()) # B(找到就返回)多态#
多态允许不同类的对象对同一消息做出不同的响应。
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
def make_sound(animal):
"""多态函数"""
print(animal.speak())
dog = Dog()
cat = Cat()
make_sound(dog) # 汪汪!
make_sound(cat) # 喵喵!属性访问控制#
私有属性(约定)#
Python 使用单下划线前缀表示"内部使用"(约定,不是强制)。
class BankAccount:
def __init__(self, balance):
self._balance = balance # 约定:内部使用
def get_balance(self):
return self._balance
account = BankAccount(1000)
print(account._balance) # 仍然可以访问(只是约定)名称改写(Name Mangling)#
使用双下划线前缀会触发名称改写,使属性更难直接访问。
class BankAccount:
def __init__(self, balance):
self.__balance = balance # 名称改写
def get_balance(self):
return self.__balance
account = BankAccount(1000)
# print(account.__balance) # 错误!AttributeError
print(account._BankAccount__balance) # 1000(仍然可以访问,但更困难)
print(account.get_balance()) # 1000(推荐方式)属性装饰器(Property)#
使用 @property 装饰器可以将方法转换为属性访问,实现数据封装和验证。
三种装饰器#
@property:定义 getter(读取器)@属性名.setter:定义 setter(设置器)@属性名.deleter:定义 deleter(删除器)
基本示例#
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""获取半径"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径(带验证)"""
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
@radius.deleter
def radius(self):
"""删除半径"""
del self._radius
@property
def area(self):
"""计算面积(只读属性,无setter)"""
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 5(像属性一样访问,无需括号)
print(circle.area) # 78.53975
circle.radius = 10 # 使用setter
print(circle.area) # 314.159
del circle.radius # 使用deleter
# circle.area = 100 # 错误!只读属性(没有setter)使用规则#
- 命名规则:
@property、@属性名.setter、@属性名.deleter的方法名必须相同 - 访问方式:
- Getter:
obj.属性(无括号,像普通属性) - Setter:
obj.属性 = 值 - Deleter:
del obj.属性
- Getter:
- 只读属性:只定义
@property,不定义 setter,即为只读 - 计算属性:
@property方法可以返回计算后的值,而不是直接存储的值 - 数据验证:在 setter 中添加验证逻辑,确保数据有效性
- 私有属性:通常使用下划线前缀(如
_radius)表示私有属性
抽象基类#
抽象基类(ABC)定义接口规范,强制子类实现特定方法,不能直接实例化。
基本用法#
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
"""抽象方法,子类必须实现"""
pass
@abstractmethod
def move(self):
"""抽象方法,子类必须实现"""
pass
def sleep(self):
"""普通方法,子类可以直接使用"""
return "睡觉中..."
class Dog(Animal):
def speak(self):
return "汪汪!"
def move(self):
return "跑步"
class Cat(Animal):
def speak(self):
return "喵喵!"
def move(self):
return "跳跃"
# animal = Animal() # 错误!不能实例化抽象类
dog = Dog()
print(dog.speak()) # 汪汪!
print(dog.sleep()) # 睡觉中...抽象属性#
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def area(self):
"""抽象属性,子类必须实现"""
pass
@abstractmethod
def draw(self):
pass
class Circle(Shape):
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14159 * self._radius ** 2
def draw(self):
return f"绘制半径为{self._radius}的圆"使用规则#
- 继承 ABC:类必须继承
ABC或使用metaclass=ABCMeta - 抽象方法:使用
@abstractmethod装饰器标记,子类必须实现 - 不能实例化:包含抽象方法的类不能直接创建实例
- 强制实现:子类必须实现所有抽象方法,否则仍是抽象类
- 普通方法:抽象基类中可以定义普通方法,子类可以直接使用
- 接口规范:用于定义接口契约,确保子类实现必要的方法
最佳实践#
- 使用有意义的类名(PascalCase)
- 每个类应该有文档字符串
- 合理使用继承,避免过度继承
- 优先使用组合而非继承
- 使用属性装饰器控制属性访问
- 理解 MRO 和多重继承
下一步#
掌握了面向对象编程后,可以学习: