Python 面向对象(二):继承与封装的深度探索

Python 面向对象(二):继承与封装的深度探索

在上一篇文章中,我们初步认识了面向对象编程的基本概念,包括类、实例、属性和方法等核心元素。今天我们将聚焦于面向对象中实现代码重用的核心机制 —— 继承,同时深入探讨封装的高级用法和多态的实践意义。这些知识将帮助我们写出更灵活、更易维护的代码。

一、继承:站在父类的肩膀上

继承是面向对象编程实现代码重用的核心手段。通过继承,我们可以从已有的类(父类)中 “继承” 属性和方法,同时在此基础上扩展新的功能。这种机制就像现实世界中 “子承父业”—— 子女继承父母的财产,同时可以开创自己的事业。

继承的基本使用

在 Python 中,定义子类时只需在类名后的括号中指定父类即可。我们用一个 “厨房电器” 的例子来理解继承的核心逻辑:

# 父类:厨房电器

class KitchenAppliance:

def __init__(self, brand, power):

self.brand = brand # 品牌(非私有属性)

self.power = power # 功率(非私有属性)

self.__working_hours = 0 # 工作时长(私有属性,双下划线开头)

def start(self):

print(f"{self.brand}电器开始工作,功率{self.power}W")

def __record_working(self): # 私有方法

self.__working_hours += 1

print(f"累计工作{self.__working_hours}小时")

# 子类:咖啡机(继承自厨房电器)

class CoffeeMachine(KitchenAppliance):

pass # 暂时不添加任何内容

# 创建子类实例

my_coffee = CoffeeMachine("飞利浦", 1200)

my_coffee.start() # 输出:飞利浦电器开始工作,功率1200W(继承父类方法)

print(my_coffee.brand) # 输出:飞利浦(访问继承的非私有属性)

# my_coffee.__record_working() # 报错:无法访问父类私有方法

# print(my_coffee.__working_hours) # 报错:无法访问父类私有属性

这个例子完美诠释了继承的第一句核心规则:子类拥有父类非私有化的属性和方法,但无法直接访问私有成员。父类中的__working_hours(私有属性)和__record_working(私有方法)被封装在父类内部,子类无法直接使用。

子类的扩展:添加专属属性和方法

继承的第二句规则是 “子类可以拥有自己的属性和方法”。我们给CoffeeMachine添加专属功能:

class CoffeeMachine(KitchenAppliance):

def __init__(self, brand, power, cup_capacity):

# 调用父类构造方法初始化继承的属性

super().__init__(brand, power)

self.cup_capacity = cup_capacity # 新增属性:一次可制作的杯数

# 新增方法:制作咖啡

def make_coffee(self, type_):

print(f"制作{type_}咖啡,一次可做{self.cup_capacity}杯")

# 使用扩展后的子类

my_coffee = CoffeeMachine("德龙", 1500, 4)

my_coffee.start() # 继承的父类方法

my_coffee.make_coffee("拿铁") # 子类专属方法:制作拿铁咖啡,一次可做4杯

print(my_coffee.cup_capacity) # 访问子类专属属性:4

子类通过super().__init__()调用父类的构造方法,确保父类的属性被正确初始化,同时添加了cup_capacity属性和make_coffee方法,实现了对父类的扩展。

重写父类方法:用自己的方式实现功能

当子类需要对父类的方法进行个性化实现时,可以重写(Override)父类方法,这就是继承的第三句规则:“子类可以用自己的方式实现父类的方法”。

class CoffeeMachine(KitchenAppliance):

# (省略__init__和make_coffee方法,同上)

# 重写父类的start方法

def start(self):

print(f"{self.brand}咖啡机启动,预热中...功率{self.power}W")

# 对比父类和子类的方法

appliance = KitchenAppliance("海尔", 800)

appliance.start() # 输出:海尔电器开始工作,功率800W(父类方法)

coffee = CoffeeMachine("德龙", 1500, 4)

coffee.start() # 输出:德龙咖啡机启动,预热中...功率1500W(重写后的方法)

重写后,子类实例调用start()时会执行自己的实现,而不是父类的方法。这种机制让子类在保留方法名的同时,实现了个性化功能。

调用父类方法:在重写中复用父类逻辑

有时我们需要在重写的方法中保留父类的功能,这时可以通过super()函数或父类名调用父类方法:

class CoffeeMachine(KitchenAppliance):

def start(self):

# 方式1:用super()调用父类方法

super().start()

# 补充子类自己的逻辑

print("咖啡机开始研磨咖啡豆...")

def clean(self):

# 方式2:用父类名调用父类方法(需传入self)

KitchenAppliance.start(self)

print("咖啡机进行自动清洗...")

coffee = CoffeeMachine("德龙", 1500, 4)

coffee.start()

# 输出:

# 德龙电器开始工作,功率1500W(父类方法)

# 咖啡机开始研磨咖啡豆...(子类补充逻辑)

coffee.clean()

# 输出:

# 德龙电器开始工作,功率1500W(父类方法)

# 咖啡机进行自动清洗...(子类逻辑)

两种方式的区别在于:super()会自动处理继承关系,在多重继承中更安全;而父类名调用方式更直接,但在多重继承中可能引发问题。

二、多重继承:同时继承多个父类

Python 支持多重继承,即一个子类可以同时继承多个父类。语法上只需在类名后的括号中用逗号分隔多个父类:

# 父类1:带定时功能的设备

class TimedDevice:

def set_timer(self, minutes):

print(f"设置定时器:{minutes}分钟后自动关闭")

# 父类2:带显示功能的设备

class DisplayDevice:

def show_status(self):

print(f"{self.brand}设备当前状态:运行中")

# 子类:智能烤箱(同时继承两个父类)

class SmartOven(KitchenAppliance, TimedDevice, DisplayDevice):

def bake(self, food):

print(f"烤制{food}中...")

# 使用多重继承的子类

oven = SmartOven("美的", 2000)

oven.start() # 继承自KitchenAppliance

oven.set_timer(30) # 继承自TimedDevice

oven.show_status() # 继承自DisplayDevice

oven.bake("鸡翅") # 子类自己的方法

多重继承虽然强大,但可能引发 “菱形问题”(多个父类最终继承自同一个基类时的方法调用冲突)。Python 通过MRO(方法解析顺序) 解决这个问题,即按照类名.__mro__显示的顺序查找方法:

print(SmartOven.__mro__)

# 输出:

# (<class '__main__.SmartOven'>, <class '__main__.KitchenAppliance'>,

# <class '__main__.TimedDevice'>, <class '__main__.DisplayDevice'>,

# <class 'object'>)

调用方法时,Python 会按 MRO 顺序查找,找到第一个匹配的方法就执行。

三、实用内置函数与属性:对象信息的查询工具

除了基础的type()和isinstance(),Python 还提供了多个实用工具用于查询对象信息:

1. type()与isinstance():判断对象类型

  • type(obj):返回对象的类型
  • isinstance(obj, cls):判断对象是否是指定类(或其子类)的实例
oven = SmartOven("美的", 2000)

print(type(oven)) # 输出:<class '__main__.SmartOven'>

print(isinstance(oven, SmartOven)) # 输出:True

print(isinstance(oven, KitchenAppliance)) # 输出:True(子类实例也是父类的实例)

2. issubclass():判断类的继承关系

issubclass(sub, parent)用于检查sub是否是parent的子类:

print(issubclass(SmartOven, KitchenAppliance)) # 输出:True

print(issubclass(CoffeeMachine, TimedDevice)) # 输出:False

3. dir():查看对象的所有属性和方法

dir(obj)返回对象所有可用的属性和方法列表:

coffee = CoffeeMachine("德龙", 1500, 4)

print(dir(coffee)) # 输出包含brand、power、cup_capacity等属性,以及start、make_coffee等方法

4. __dict__属性:对象的属性字典

obj.__dict__返回一个字典,包含对象的所有实例属性及其值:

print(coffee.__dict__)

# 输出:{'brand': '德龙', 'power': 1500, 'cup_capacity': 4}

5. hasattr():检查对象是否有指定属性

hasattr(obj, name)判断对象是否包含名为name的属性:

print(hasattr(coffee, "brand")) # 输出:True

print(hasattr(coffee, "weight")) # 输出:False

这些工具在调试和动态操作对象时非常实用,比如通过hasattr()和getattr()可以实现灵活的属性访问。

四、封装的高级用法:私有成员与 property 属性

封装的核心是隐藏对象的内部状态,只通过公开接口与外部交互。除了基础的私有属性和方法,Python 还提供了property机制用于属性的精细化控制。

私有成员的访问控制

父类的私有成员(以双下划线__开头)只能在父类内部访问,子类和外部都无法直接访问。但可以通过父类提供的公开方法间接访问:

class KitchenAppliance:

def __init__(self, brand):

self.__brand = brand # 私有属性

# 公开方法:获取私有属性

def get_brand(self):

return self.__brand

# 公开方法:设置私有属性(带验证)

def set_brand(self, new_brand):

if isinstance(new_brand, str) and new_brand.strip():

self.__brand = new_brand

else:

print("品牌名称必须是非空字符串")

appliance = KitchenAppliance("苏泊尔")

print(appliance.get_brand()) # 输出:苏泊尔(通过公开方法访问)

appliance.set_brand("") # 输出:品牌名称必须是非空字符串(验证生效)

私有方法的定义与使用

私有方法同样以__开头,只能在类内部调用,用于封装内部逻辑:

class BankCard:

def __init__(self, balance=0):

self.__balance = balance

def deposit(self, amount):

if amount > 0:

self.__balance += amount

self.__log_transaction(f"存款{amount}元") # 调用私有方法

else:

print("存款金额必须为正数")

# 私有方法:记录交易日志

def __log_transaction(self, msg):

print(f"交易记录:{msg}")

card = BankCard()

card.deposit(500) # 输出:交易记录:存款500元(内部调用私有方法)

# card.__log_transaction("测试") # 报错:无法外部调用

property 属性:让属性访问更优雅

property可以将方法伪装成属性,既保留了方法的逻辑控制,又能像属性一样直接访问。有两种实现方式:

方式 1:装饰器语法
class Book:

def __init__(self, price):

self.__price = price # 私有属性

@property

def price(self): # 相当于getter

return f"¥{self.__price:.2f}"

@price.setter

def price(self, new_price): # 相当于setter

if new_price > 0:

self.__price = new_price

else:

print("价格必须大于0")

book = Book(39.9)

print(book.price) # 输出:¥39.90(像属性一样访问)

book.price = 49.9 # 像属性一样赋值

print(book.price) # 输出:¥49.90

book.price = -10 # 输出:价格必须大于0(验证生效)
方式 2:类属性语法
class Book:

def __init__(self, price):

self.__price = price

def get_price(self):

return f"¥{self.__price:.2f}"

def set_price(self, new_price):

if new_price > 0:

self.__price = new_price

else:

print("价格必须大于0")

# 定义property属性

price = property(get_price, set_price)

# 使用方式和装饰器语法完全一致

book = Book(29.9)

print(book.price) # 输出:¥29.90

property让我们可以在不改变外部接口的情况下,对属性的访问和赋值添加逻辑控制(如验证、格式化),是封装思想的高级应用。

五、多态:同一接口,不同实现

多态是指不同的对象对同一方法调用做出不同响应的能力。它依赖于继承和方法重写,让我们可以用统一的接口处理不同类型的对象。

# 定义统一接口函数

def use_appliance(appliance):

appliance.start() # 调用统一的start方法

# 创建不同的子类实例

coffee = CoffeeMachine("德龙", 1500, 4)

oven = SmartOven("美的", 2000)

# 同一接口处理不同对象

use_appliance(coffee) # 输出:德龙电器开始工作,功率1500W(咖啡机的start)

use_appliance(oven) # 输出:美的电器开始工作,功率2000W(烤箱的start)

即使未来添加新的厨房电器子类(如Juicer),只要它继承KitchenAppliance并实现start()方法,use_appliance函数无需任何修改就能正常工作。这种 “开闭原则” 正是多态的价值所在 —— 扩展新功能时无需修改原有代码。

六、总结与实践建议

本文深入探讨了面向对象的核心机制:

  • 继承通过 “父类定义共性,子类实现个性” 实现代码重用
  • 多重继承需注意 MRO 顺序,避免方法调用冲突
  • 私有成员和property属性强化了封装的安全性和灵活性
  • 多态通过统一接口实现了代码的扩展性

实践中建议:

  1. 继承不要超过 3 层,避免代码逻辑过于复杂
  1. 优先使用组合(将对象作为属性)而非多重继承解决复杂问题
  1. 用property替代直接暴露属性,为未来扩展留有余地
  1. 设计类时考虑多态性,让接口更通用

下一篇我们将探讨魔法方法,敬请期待!如果您在实践中遇到问题,欢迎在评论区交流讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值