引言
在前面的章节中,我们学习了Python的基础语法、控制结构、函数以及内置数据结构。这些知识为我们编写程序提供了坚实的基础。然而,随着程序复杂度的增加,我们需要一种更高级的编程范式来组织和管理代码。
面向对象编程(Object-Oriented Programming,简称OOP)是一种重要的编程范式,它将现实世界中的事物抽象为对象,通过对象之间的交互来解决问题。Python完全支持面向对象编程,掌握OOP概念对于编写大型、可维护的程序至关重要。
在本章中,我们将深入学习面向对象编程的基本概念,包括类和对象的定义、属性和方法、封装、继承、多态等核心特性。通过实际的例子,你将理解如何使用OOP思想来设计和实现程序。
学习目标
完成本章学习后,你将能够:
- 理解面向对象编程的基本概念和优势
- 掌握类和对象的定义与使用方法
- 理解属性和方法的概念及其访问方式
- 掌握封装的概念和实现方法
- 理解继承的概念,能够创建和使用继承关系
- 理解多态的概念及其在Python中的实现
- 编写结构清晰、可维护的面向对象程序
- 理解特殊方法(魔术方法)的作用和使用
核心知识点讲解
什么是面向对象编程?
面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,通过对象之间的交互来解决问题。OOP的核心思想是将数据(属性)和操作数据的方法(函数)封装在一起,形成一个独立的实体——对象。
OOP有四大基本特性:
- 封装(Encapsulation):将数据和操作数据的方法组合在一起,隐藏内部实现细节
- 继承(Inheritance):子类可以继承父类的属性和方法,实现代码复用
- 多态(Polymorphism):同一个接口可以有不同的实现方式
- 抽象(Abstraction):隐藏复杂的实现细节,只暴露必要的接口
类和对象
在Python中,类是创建对象的蓝图或模板,而对象是类的实例。
定义类
使用class关键字定义类:
class Person:
"""人的类"""
pass # 占位符,表示暂时没有内容
创建对象(实例化)
通过调用类名来创建对象:
# 创建Person类的实例
person1 = Person()
person2 = Person()
print(type(person1)) # 输出: <class '__main__.Person'>
构造方法和属性
__init__方法是类的构造方法,在创建对象时自动调用:
class Person:
def __init__(self, name, age):
"""构造方法"""
self.name = name # 实例属性
self.age = age # 实例属性
def introduce(self):
"""实例方法"""
print(f"你好,我是{self.name},今年{self.age}岁")
# 创建对象并传入参数
person1 = Person("张三", 25)
person2 = Person("李四", 30)
# 访问属性
print(person1.name) # 输出: 张三
print(person2.age) # 输出: 30
# 调用方法
person1.introduce() # 输出: 你好,我是张三,今年25岁
属性
属性是与对象相关联的数据。
实例属性
实例属性属于特定的对象实例:
class Car:
def __init__(self, brand, model):
self.brand = brand # 实例属性
self.model = model # 实例属性
self.mileage = 0 # 实例属性,初始里程为0
car1 = Car("丰田", "卡罗拉")
car2 = Car("本田", "雅阁")
print(car1.brand) # 输出: 丰田
print(car2.model) # 输出: 雅阁
类属性
类属性属于类本身,被所有实例共享:
class Car:
wheels = 4 # 类属性,所有汽车都有4个轮子
def __init__(self, brand, model):
self.brand = brand
self.model = model
car1 = Car("丰田", "卡罗拉")
car2 = Car("本田", "雅阁")
print(Car.wheels) # 输出: 4(通过类名访问)
print(car1.wheels) # 输出: 4(通过实例访问)
print(car2.wheels) # 输出: 4
# 修改类属性
Car.wheels = 6
print(car1.wheels) # 输出: 6
方法
方法是与对象相关联的函数。
实例方法
实例方法需要通过对象实例调用,第一个参数通常是self:
class Calculator:
def __init__(self):
self.result = 0
def add(self, num):
"""加法"""
self.result += num
return self.result
def subtract(self, num):
"""减法"""
self.result -= num
return self.result
def get_result(self):
"""获取结果"""
return self.result
calc = Calculator()
print(calc.add(5)) # 输出: 5
print(calc.subtract(2)) # 输出: 3
print(calc.add(10)) # 输出: 13
类方法
类方法使用@classmethod装饰器定义,第一个参数是cls(类本身):
class MathUtils:
pi = 3.14159
@classmethod
def circle_area(cls, radius):
"""计算圆的面积"""
return cls.pi * radius ** 2
@classmethod
def cylinder_volume(cls, radius, height):
"""计算圆柱体体积"""
return cls.circle_area(radius) * height
# 通过类名调用类方法
area = MathUtils.circle_area(5)
volume = MathUtils.cylinder_volume(3, 10)
print(f"半径为5的圆面积: {area:.2f}")
print(f"半径为3,高为10的圆柱体体积: {volume:.2f}")
静态方法
静态方法使用@staticmethod装饰器定义,不需要self或cls参数:
class StringUtils:
@staticmethod
def is_palindrome(text):
"""检查字符串是否为回文"""
cleaned = text.lower().replace(" ", "")
return cleaned == cleaned[::-1]
@staticmethod
def word_count(text):
"""统计单词数量"""
return len(text.split())
# 通过类名调用静态方法
print(StringUtils.is_palindrome("A man a plan a canal Panama")) # 输出: True
print(StringUtils.word_count("Hello world Python programming")) # 输出: 4
封装
封装是将数据和操作数据的方法组合在一起,并控制对内部数据的访问。
访问控制
Python使用命名约定来表示访问级别:
- 公有(public):正常命名,可以随意访问
- 受保护(protected):以单下划线开头,表示不应该在类外部直接访问
- 私有(private):以双下划线开头,Python会对其进行名称改写
class BankAccount:
def __init__(self, account_number, initial_balance):
self.account_number = account_number # 公有属性
self._account_holder = "匿名" # 受保护属性
self.__balance = initial_balance # 私有属性
def deposit(self, amount):
"""存款"""
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
"""取款"""
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
def get_balance(self):
"""获取余额"""
return self.__balance
def _get_account_info(self):
"""受保护方法"""
return f"账户: {self.account_number}, 持有人: {self._account_holder}"
def __validate_transaction(self):
"""私有方法"""
# 私有方法的名称会被改写为 _BankAccount__validate_transaction
return True
account = BankAccount("123456789", 1000)
print(account.account_number) # 正常访问公有属性
# print(account.__balance) # 这会报错
print(account.get_balance()) # 通过方法访问私有属性
# 访问私有属性(不推荐)
print(account._BankAccount__balance) # 输出: 1000
属性装饰器
使用@property装饰器可以将方法变成属性访问:
class Temperature:
def __init__(self, celsius=0):
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
@property
def fahrenheit(self):
"""获取华氏度"""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
"""设置华氏度"""
self.celsius = (value - 32) * 5/9
temp = Temperature(25)
print(f"摄氏度: {temp.celsius}") # 输出: 摄氏度: 25
print(f"华氏度: {temp.fahrenheit}") # 输出: 华氏度: 77.0
temp.celsius = 30
print(f"摄氏度: {temp.celsius}") # 输出: 摄氏度: 30
print(f"华氏度: {temp.fahrenheit}") # 输出: 华氏度: 86.0
继承
继承允许我们创建一个新类(子类),继承现有类(父类)的属性和方法。
基本继承
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
print(f"{self.name} 发出了声音")
def info(self):
print(f"我是 {self.name},属于 {self.species} species")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "犬科") # 调用父类构造方法
self.breed = breed
def make_sound(self):
print(f"{self.name} 汪汪叫")
def fetch(self):
print(f"{self.name} 正在捡球")
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, "猫科")
self.color = color
def make_sound(self):
print(f"{self.name} 喵喵叫")
def climb(self):
print(f"{self.name} 正在爬树")
# 创建对象
dog = Dog("旺财", "金毛")
cat = Cat("咪咪", "橘色")
# 调用方法
dog.make_sound() # 输出: 旺财 汪汪叫
dog.fetch() # 输出: 旺财 正在捡球
dog.info() # 输出: 我是 旺财,属于 犬科 species
cat.make_sound() # 输出: 咪咪 喵喵叫
cat.climb() # 输出: 咪咪 正在爬树
cat.info() # 输出: 我是 咪咪,属于 猫科 species
多重继承
Python支持多重继承,一个类可以继承多个父类:
class Flyable:
def fly(self):
print(f"{self.name} 正在飞翔")
class Swimmable:
def swim(self):
print(f"{self.name} 正在游泳")
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name):
super().__init__(name, "鸭科")
def make_sound(self):
print(f"{self.name} 嘎嘎叫")
duck = Duck("唐老鸭")
duck.make_sound() # 输出: 唐老鸭 嘎嘎叫
duck.fly() # 输出: 唐老鸭 正在飞翔
duck.swim() # 输出: 唐老鸭 正在游泳
多态
多态是指同一个接口可以有不同的实现方式。
class Shape:
def area(self):
pass
def perimeter(self):
pass
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)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
# 多态的体现
shapes = [
Rectangle(5, 3),
Circle(4),
Rectangle(2, 8)
]
for shape in shapes:
print(f"面积: {shape.area():.2f}, 周长: {shape.perimeter():.2f}")
特殊方法(魔术方法)
Python提供了一些特殊方法,以双下划线开头和结尾,用于实现特定的功能:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
"""定义str()函数的行为"""
return f"Vector({self.x}, {self.y})"
def __repr__(self):
"""定义repr()函数的行为"""
return f"Vector(x={self.x}, y={self.y})"
def __add__(self, other):
"""定义加法运算"""
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
"""定义相等比较"""
return self.x == other.x and self.y == other.y
def __len__(self):
"""定义len()函数的行为"""
return int((self.x ** 2 + self.y ** 2) ** 0.5)
def __getitem__(self, index):
"""定义索引访问"""
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("索引必须是0或1")
# 使用特殊方法
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # 输出: Vector(3, 4)
print(v1 + v2) # 输出: Vector(4, 6)
print(v1 == v2) # 输出: False
print(len(v1)) # 输出: 5
print(v1[0], v1[1]) # 输出: 3 4
代码示例与实战
示例1:图书管理系统
class Book:
"""图书类"""
def __init__(self, isbn, title, author, publication_year):
self.isbn = isbn
self.title = title
self.author = author
self.publication_year = publication_year
self.is_borrowed = False
self.borrower = None
def __str__(self):
status = "已借出" if self.is_borrowed else "在馆"
return f"《{self.title}》 - {self.author} ({self.publication_year}) [{status}]"
class LibraryMember:
"""图书馆会员类"""
def __init__(self, member_id, name):
self.member_id = member_id
self.name = name
self.borrowed_books = []
def borrow_book(self, book):
"""借书"""
if not book.is_borrowed:
book.is_borrowed = True
book.borrower = self
self.borrowed_books.append(book)
print(f"{self.name} 借阅了《{book.title}》")
return True
else:
print(f"《{book.title}》已被借出")
return False
def return_book(self, book):
"""还书"""
if book in self.borrowed_books:
book.is_borrowed = False
book.borrower = None
self.borrowed_books.remove(book)
print(f"{self.name} 归还了《{book.title}》")
return True
else:
print(f"{self.name} 没有借阅《{book.title}》")
return False
class Library:
"""图书馆类"""
def __init__(self):
self.books = []
self.members = []
def add_book(self, book):
"""添加图书"""
self.books.append(book)
print(f"已添加图书: {book}")
def add_member(self, member):
"""添加会员"""
self.members.append(member)
print(f"已添加会员: {member.name}")
def search_books(self, keyword):
"""搜索图书"""
results = []
for book in self.books:
if keyword.lower() in book.title.lower() or keyword.lower() in book.author.lower():
results.append(book)
return results
def display_all_books(self):
"""显示所有图书"""
print("\n=== 图书馆藏书 ===")
for book in self.books:
print(book)
print("=" * 30)
def main():
# 创建图书馆
library = Library()
# 添加图书
book1 = Book("978-0134685991", "Effective Python", "Brett Slatkin", 2019)
book2 = Book("978-1449355739", "Python Cookbook", "David Beazley", 2013)
book3 = Book("978-1491946008", "Fluent Python", "Luciano Ramalho", 2015)
library.add_book(book1)
library.add_book(book2)
library.add_book(book3)
# 添加会员
member1 = LibraryMember("M001", "张三")
member2 = LibraryMember("M002", "李四")
library.add_member(member1)
library.add_member(member2)
# 显示所有图书
library.display_all_books()
# 借书和还书
member1.borrow_book(book1)
member2.borrow_book(book1) # 尝试借已被借出的书
library.display_all_books()
member1.return_book(book1)
library.display_all_books()
# 搜索图书
print("\n搜索 'Python' 的结果:")
results = library.search_books("Python")
for book in results:
print(book)
if __name__ == "__main__":
main()
示例2:几何图形计算器
import math
class Shape:
"""形状基类"""
def area(self):
"""计算面积"""
raise NotImplementedError("子类必须实现area方法")
def perimeter(self):
"""计算周长"""
raise NotImplementedError("子类必须实现perimeter方法")
def __str__(self):
return f"{self.__class__.__name__}"
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)
def __str__(self):
return f"矩形(宽={self.width}, 高={self.height})"
class Circle(Shape):
"""圆形类"""
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
def __str__(self):
return f"圆形(半径={self.radius})"
class Triangle(Shape):
"""三角形类"""
def __init__(self, a, b, c):
# 检查是否能构成三角形
if a + b <= c or a + c <= b or b + c <= a:
raise ValueError("三边长度不能构成三角形")
self.a = a
self.b = b
self.c = c
def area(self):
# 使用海伦公式计算面积
s = (self.a + self.b + self.c) / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
def __str__(self):
return f"三角形(边长={self.a}, {self.b}, {self.c})"
class ShapeCalculator:
"""形状计算器"""
def __init__(self):
self.shapes = []
def add_shape(self, shape):
"""添加形状"""
self.shapes.append(shape)
print(f"已添加形状: {shape}")
def calculate_total_area(self):
"""计算总面积"""
total = sum(shape.area() for shape in self.shapes)
return total
def calculate_total_perimeter(self):
"""计算总周长"""
total = sum(shape.perimeter() for shape in self.shapes)
return total
def display_shapes(self):
"""显示所有形状及其信息"""
print("\n=== 形状列表 ===")
for i, shape in enumerate(self.shapes, 1):
print(f"{i}. {shape}")
print(f" 面积: {shape.area():.2f}")
print(f" 周长: {shape.perimeter():.2f}")
print("=" * 30)
def main():
calculator = ShapeCalculator()
# 添加各种形状
try:
calculator.add_shape(Rectangle(5, 3))
calculator.add_shape(Circle(4))
calculator.add_shape(Triangle(3, 4, 5))
calculator.add_shape(Rectangle(2, 8))
calculator.add_shape(Circle(2.5))
except ValueError as e:
print(f"错误: {e}")
# 显示所有形状
calculator.display_shapes()
# 计算总计
total_area = calculator.calculate_total_area()
total_perimeter = calculator.calculate_total_perimeter()
print(f"\n总计:")
print(f"总面积: {total_area:.2f}")
print(f"总周长: {total_perimeter:.2f}")
if __name__ == "__main__":
main()
示例3:员工管理系统
from abc import ABC, abstractmethod
from datetime import datetime
class Employee(ABC):
"""员工抽象基类"""
def __init__(self, emp_id, name, hire_date):
self.emp_id = emp_id
self.name = name
self.hire_date = datetime.strptime(hire_date, "%Y-%m-%d")
@abstractmethod
def calculate_salary(self):
"""计算薪资(抽象方法)"""
pass
def get_years_of_service(self):
"""计算工作年限"""
today = datetime.now()
years = today.year - self.hire_date.year
if (today.month, today.day) < (self.hire_date.month, self.hire_date.day):
years -= 1
return years
def __str__(self):
return f"员工ID: {self.emp_id}, 姓名: {self.name}, 入职日期: {self.hire_date.strftime('%Y-%m-%d')}"
class FullTimeEmployee(Employee):
"""全职员工"""
def __init__(self, emp_id, name, hire_date, monthly_salary):
super().__init__(emp_id, name, hire_date)
self.monthly_salary = monthly_salary
def calculate_salary(self):
"""计算月薪"""
# 根据工作年限给予奖金
years = self.get_years_of_service()
bonus = self.monthly_salary * 0.01 * years # 每年1%的奖金
return self.monthly_salary + bonus
def __str__(self):
return super().__str__() + f", 类型: 全职, 月薪: {self.monthly_salary}"
class PartTimeEmployee(Employee):
"""兼职员工"""
def __init__(self, emp_id, name, hire_date, hourly_rate, hours_worked):
super().__init__(emp_id, name, hire_date)
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked
def calculate_salary(self):
"""计算工资"""
return self.hourly_rate * self.hours_worked
def __str__(self):
return super().__str__() + f", 类型: 兼职, 时薪: {self.hourly_rate}, 工作小时: {self.hours_worked}"
class Manager(FullTimeEmployee):
"""经理"""
def __init__(self, emp_id, name, hire_date, monthly_salary, team_size):
super().__init__(emp_id, name, hire_date, monthly_salary)
self.team_size = team_size
def calculate_salary(self):
"""计算经理薪资(包含管理津贴)"""
base_salary = super().calculate_salary()
management_bonus = self.team_size * 500 # 每个团队成员500元管理津贴
return base_salary + management_bonus
def __str__(self):
return super().__str__() + f", 团队规模: {self.team_size}"
class EmployeeManager:
"""员工管理器"""
def __init__(self):
self.employees = []
def add_employee(self, employee):
"""添加员工"""
self.employees.append(employee)
print(f"已添加员工: {employee.name}")
def remove_employee(self, emp_id):
"""移除员工"""
for employee in self.employees:
if employee.emp_id == emp_id:
self.employees.remove(employee)
print(f"已移除员工: {employee.name}")
return
print(f"未找到ID为 {emp_id} 的员工")
def find_employee(self, emp_id):
"""查找员工"""
for employee in self.employees:
if employee.emp_id == emp_id:
return employee
return None
def calculate_payroll(self):
"""计算工资单"""
print("\n=== 工资单 ===")
total_payroll = 0
for employee in self.employees:
salary = employee.calculate_salary()
total_payroll += salary
print(f"{employee.name}: ¥{salary:.2f}")
print(f"总工资支出: ¥{total_payroll:.2f}")
print("=" * 30)
def display_all_employees(self):
"""显示所有员工"""
print("\n=== 员工列表 ===")
for employee in self.employees:
print(employee)
print(f" 工作年限: {employee.get_years_of_service()}年")
print(f" 当前薪资: ¥{employee.calculate_salary():.2f}")
print("-" * 40)
print("=" * 40)
def main():
manager = EmployeeManager()
# 添加员工
emp1 = FullTimeEmployee("FT001", "张三", "2020-01-15", 8000)
emp2 = PartTimeEmployee("PT001", "李四", "2021-03-10", 50, 80)
emp3 = Manager("M001", "王五", "2019-06-01", 15000, 5)
emp4 = FullTimeEmployee("FT002", "赵六", "2022-09-20", 7000)
manager.add_employee(emp1)
manager.add_employee(emp2)
manager.add_employee(emp3)
manager.add_employee(emp4)
# 显示所有员工
manager.display_all_employees()
# 计算工资单
manager.calculate_payroll()
# 查找特定员工
employee = manager.find_employee("M001")
if employee:
print(f"\n找到员工: {employee.name}")
print(f"工作年限: {employee.get_years_of_service()}年")
print(f"当前薪资: ¥{employee.calculate_salary():.2f}")
if __name__ == "__main__":
main()
小结与回顾
在本章中,我们深入学习了面向对象编程的核心概念:
-
类和对象:
- 类是创建对象的模板,对象是类的实例
- 使用
__init__方法初始化对象属性 - 通过点号访问属性和调用方法
-
属性和方法:
- 实例属性属于特定对象,类属性被所有实例共享
- 实例方法、类方法和静态方法有不同的用途和调用方式
-
封装:
- 将数据和方法组合在一起,隐藏内部实现细节
- 使用命名约定控制访问级别
- 通过属性装饰器实现更优雅的属性访问
-
继承:
- 子类可以继承父类的属性和方法
- 使用
super()调用父类方法 - 支持多重继承
-
多态:
- 同一个接口可以有不同的实现方式
- 提高代码的灵活性和可扩展性
-
特殊方法:
- 实现自定义类的特殊行为
- 让自定义类更像内置类型
通过实际的代码示例,我们不仅掌握了理论知识,还学会了如何在实际项目中应用面向对象编程思想。OOP是编写大型、可维护程序的重要工具。
在下一章中,我们将学习异常处理机制,让你的程序能够优雅地处理错误和异常情况。
练习与挑战
基础练习
- 创建一个
BankAccount类,包含账户号、余额属性和存款、取款、查询余额等方法。 - 编写一个
Vehicle基类和Car、Truck子类,实现继承关系。 - 创建一个
Calculator类,使用属性装饰器实现对计算结果的访问控制。 - 实现一个简单的
Student类,包含姓名、学号、成绩等属性和相关方法。
进阶挑战
- 设计一个完整的学校管理系统,包含学生、教师、课程等类,实现复杂的继承和多态关系。
- 创建一个图形用户界面组件库,使用继承和多态实现按钮、文本框、标签等组件。
- 实现一个简单的游戏框架,使用面向对象思想设计角色、道具、关卡等类。
- 编写一个电子商务系统,包含商品、购物车、订单、用户等类,体现封装和继承的使用。
思考题
- 什么情况下应该使用类属性而不是实例属性?
- 多重继承有什么优缺点?在什么情况下使用比较合适?
- 如何设计良好的类接口,使其既功能完整又易于使用?
- 什么时候应该使用抽象基类?它有什么优势?
扩展阅读
- Python官方文档 - 类 - 官方文档中关于类的详细介绍
- Python官方文档 - 数据模型 - 关于特殊方法和数据模型的详细说明
- 《流畅的Python》- 深入理解Python面向对象编程的经典书籍
- Real Python - Object-Oriented Programming - 关于Python面向对象编程的详细教程
- 《设计模式:可复用面向对象软件的基础》- 学习常见的设计模式,提高面向对象设计能力
- Python for Everybody - Objects - 面向初学者的面向对象编程教学
通过本章的学习,你应该已经掌握了Python面向对象编程的基本概念和使用方法。这些知识将帮助你编写更加模块化和可维护的程序。在下一章中,我们将学习异常处理机制,让你的程序能够优雅地处理错误和异常情况。