学习笔记:封装和单继承
一、封装
1. 什么是封装?
封装是面向对象编程的三大特性之一(封装、继承、多态)。简单来说,封装就是将数据和操作数据的方法捆绑在一起,隐藏对象的内部细节,只对外提供必要的接口。
想象一下:你的手机就是一个很好的封装例子。你不需要知道内部的电路如何工作,只需要通过屏幕、按键这些接口来操作它。
2. 为什么需要封装?
- 保护数据,防止被意外修改
- 隐藏实现细节,让使用者专注于如何使用
- 提高代码的可维护性和安全性
3. 如何在 Python 中实现封装?
在 Python 中,我们通过类来实现封装,使用不同的访问修饰符来控制属性和方法的访问权限:
- 公开属性 / 方法:默认,可在任何地方访问
- 私有属性 / 方法:在名称前加两个下划线
__
,只能在类内部访问- 受保护属性 / 方法:在名称前加一个下划线
_
,表示仅供内部或子类使用(约定)
4. 封装示例代码
class Person:# 构造方法,初始化对象def __init__(self, name, age):self.name = name # 公开属性self._height = 170 # 受保护属性(约定)self.__age = age # 私有属性# 公开方法def greet(self):return f"Hello, my name is {self.name}, I'm {self.__get_age()} years old."# 私有方法def __get_age(self):return self.__age# 提供访问私有属性的接口def get_age(self):return self.__age# 提供修改私有属性的接口def set_age(self, new_age):if new_age > 0 and new_age < 120:self.__age = new_ageelse:print("Invalid age!")# 创建对象
person = Person("Alice", 25)# 访问公开属性和方法
print(person.name) # 输出: Alice
print(person.greet()) # 输出: Hello, my name is Alice, I'm 25 years old.# 尝试访问私有属性(会报错)
# print(person.__age) # 报错: AttributeError# 通过公开接口访问私有属性
print(person.get_age()) # 输出: 25# 通过公开接口修改私有属性
person.set_age(30)
print(person.get_age()) # 输出: 30# 尝试设置无效年龄
person.set_age(150) # 输出: Invalid age!
5. 封装的使用场景
- 当你希望某些数据只能通过特定方式修改时(如上面例子中的年龄验证)
- 当类的内部实现可能会变化,但你希望保持对外接口不变时
- 当你想隐藏复杂的实现细节,只提供简单的使用方式时
二、单继承
1. 什么是继承?
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,同时可以添加自己特有的属性和方法。
单继承就是一个子类只继承一个父类。
想象一下:猫和狗都是动物,它们都有名字、会叫、会跑,但猫会抓老鼠,狗会看门。这里 "动物" 就是父类,"猫" 和 "狗" 是子类。
2. 为什么需要继承?
- 代码复用:避免重复编写相同的代码
- 建立类之间的关系,使代码结构更清晰
- 便于扩展:在不修改父类的情况下添加新功能
3. 如何在 Python 中实现单继承?
在定义类时,在类名后的括号中指定要继承的父类。
class 子类名(父类名):# 子类的属性和方法
4. 继承示例代码
# 父类
class Animal:def __init__(self, name):self.name = namedef eat(self):print(f"{self.name} is eating.")def sleep(self):print(f"{self.name} is sleeping.")# 子类,继承自Animal
class Dog(Animal):# 子类可以添加自己的方法def bark(self):print(f"{self.name} is barking: Woof! Woof!")# 子类可以重写父类的方法def sleep(self):print(f"{self.name} is sleeping in a doghouse.")# 子类,继承自Animal
class Cat(Animal):def meow(self):print(f"{self.name} is meowing: Meow! Meow!")def sleep(self):print(f"{self.name} is sleeping on the sofa.")# 创建对象
dog = Dog("Buddy")
cat = Cat("Mittens")# 调用继承的方法
dog.eat() # 输出: Buddy is eating.
cat.eat() # 输出: Mittens is eating.# 调用子类自己的方法
dog.bark() # 输出: Buddy is barking: Woof! Woof!
cat.meow() # 输出: Mittens is meowing: Meow! Meow!# 调用重写的方法
dog.sleep() # 输出: Buddy is sleeping in a doghouse.
cat.sleep() # 输出: Mittens is sleeping on the sofa.
5. 继承中的 super () 函数
super()
函数用于调用父类的方法,通常用于在子类中扩展父类的方法。
class Animal:def __init__(self, name, color):self.name = nameself.color = colorclass Bird(Animal):def __init__(self, name, color, can_fly):# 调用父类的__init__方法super().__init__(name, color)# 添加子类自己的属性self.can_fly = can_flydef introduce(self):flying_status = "can fly" if self.can_fly else "can't fly"return f"I'm {self.name}, a {self.color} bird that {flying_status}."# 创建对象
sparrow = Bird("Sparrow", "brown", True)
penguin = Bird("Penguin", "black and white", False)print(sparrow.introduce()) # 输出: I'm Sparrow, a brown bird that can fly.
print(penguin.introduce()) # 输出: I'm Penguin, a black and white bird that can't fly.
6. 单继承的使用场景
- 当多个类有共同的属性和方法时,可以将这些共同部分提取到父类中
- 当需要创建一个更具体的类,它是某个更通用类的特例时
- 当需要扩展现有类的功能,但又不想修改原有类的代码时(开放 - 封闭原则)
三、封装与继承的结合使用
在实际开发中,封装和继承通常是结合使用的。子类可以继承父类的公有和受保护成员,但不能直接访问父类的私有成员。
class Person:def __init__(self, name, age):self.name = name # 公开属性self.__age = age # 私有属性def get_age(self):return self.__agedef set_age(self, new_age):if new_age > 0 and new_age < 120:self.__age = new_ageelse:print("Invalid age!")# 学生类继承自人类
class Student(Person):def __init__(self, name, age, student_id):super().__init__(name, age)self.student_id = student_iddef introduce(self):return f"I'm {self.name}, a student with ID {self.student_id}, {self.get_age()} years old."# 创建学生对象
student = Student("Bob", 20, "S12345")
print(student.introduce()) # 输出: I'm Bob, a student with ID S12345, 20 years old.# 尝试直接访问父类的私有属性(会报错)
# print(student.__age) # 报错: AttributeError# 通过父类提供的接口访问
print(student.get_age()) # 输出: 20
四、访问私有属性和方法
1、访问私有属性和方法的方式
Python 中,私有属性和方法是通过在名称前加两个下划线__
来定义的。这种命名会触发 "名称修饰"(name mangling)机制,即解释器会将其重命名为_类名__属性名
的形式,以避免子类中的命名冲突。
因此,我们可以通过以下两种方式访问私有成员:
1. 通过类内部的公有方法访问(推荐)
这是最规范的方式,通过类内部定义的公开方法(getter/setter)来间接访问或修改私有成员,符合封装的设计思想。
class Person:def __init__(self, name, age):self.__name = name # 私有属性self.__age = age # 私有属性# 公开方法:获取私有属性def get_name(self):return self.__name# 公开方法:修改私有属性(可添加验证逻辑)def set_age(self, new_age):if 0 < new_age < 120:self.__age = new_ageelse:print("年龄必须在0-120之间")# 公开方法:调用私有方法def show_info(self):return self.__get_info() # 内部调用私有方法# 私有方法def __get_info(self):return f"姓名:{self.__name},年龄:{self.__age}"# 使用示例
p = Person("张三", 25)# 通过公有方法访问私有属性
print(p.get_name()) # 输出:张三# 通过公有方法修改私有属性
p.set_age(30)
print(p.show_info()) # 输出:姓名:张三,年龄:30# 通过公有方法调用私有方法(间接)
print(p.show_info()) # 输出:姓名:张三,年龄:30
2. 通过名称修饰后的名称直接访问(不推荐)
利用 Python 的名称修饰规则,我们可以在类外部通过_类名__私有成员名
的形式直接访问私有属性和方法。但这种方式破坏了封装性,不建议在实际开发中使用。
class Person:def __init__(self, name):self.__name = name # 私有属性def __say_hello(self): # 私有方法return f"你好,我是{self.__name}"# 使用示例
p = Person("李四")# 直接访问私有属性(名称修饰后)
print(p._Person__name) # 输出:李四# 直接调用私有方法(名称修饰后)
print(p._Person__say_hello()) # 输出:你好,我是李四
2、注意事项
不推荐直接访问:
私有成员的设计初衷是隐藏内部实现,直接通过名称修饰访问会破坏封装性,导致代码耦合度升高,难以维护。命名约定的意义:
Python 的私有机制更多是一种 "约定" 而非强制限制,目的是提醒开发者:"这些成员是内部使用的,外部不应直接修改"。子类无法直接继承私有成员:
子类不能直接访问父类的私有成员,即使通过名称修饰,也需要使用父类的类名(如_父类名__私有成员
)才能访问,这进一步保证了父类内部实现的安全性。
3、总结
方式 | 语法 | 推荐度 | 特点 |
---|---|---|---|
公有方法访问 | 类内部定义get_xxx() /set_xxx() | 推荐 | 符合封装思想,可控制访问逻辑 |
名称修饰访问 | _类名__私有成员名 | 不推荐 | 破坏封装,仅用于特殊调试场景 |
五、知识点总结
封装
- 封装是将数据和操作数据的方法捆绑在一起
- 目的是保护数据、隐藏细节、提高可维护性
- Python 中通过类实现封装,使用
__
定义私有成员,_
定义受保护成员- 通常为私有成员提供公开的 getter 和 setter 方法来访问和修改
单继承
- 继承是让一个类获得另一个类的属性和方法
- 单继承指一个子类只继承一个父类
- 目的是代码复用、建立类关系、便于扩展
- 使用
class 子类名(父类名)
语法实现继承- 使用
super()
函数调用父类的方法- 子类可以重写父类的方法,实现多态
综合
- 封装和继承通常结合使用
- 子类可以继承父类的公有和受保护成员
- 子类不能直接访问父类的私有成员,需通过父类提供的接口访问