一、封装
1. 什么是封装?
封装是面向对象编程的三大特性之一(封装、继承、多态)。简单来说,封装就是将数据和操作数据的方法捆绑在一起,隐藏对象的内部细节,只对外提供必要的接口。
想象一下:你的手机就是一个很好的封装例子。你不需要知道内部的电路如何工作,只需要通过屏幕、按键这些接口来操作它。
2. 为什么需要封装?
- 保护数据,防止被意外修改
- 隐藏实现细节,让使用者专注于如何使用
- 提高代码的可维护性和安全性
3. 如何在 Python 中实现封装?
在 Python 中,我们通过类来实现封装,使用不同的访问修饰符来控制属性和方法的访问权限:
- 公开属性 / 方法:默认,可在任何地方访问
- 私有属性 / 方法:在名称前加两个下划线
__
,只能在类内部访问- 受保护属性 / 方法:在名称前加一个下划线
_
,表示仅供内部或子类使用(约定)
4. 封装示例代码
python
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_age
else:
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 中实现单继承?
在定义类时,在类名后的括号中指定要继承的父类。
python
class 子类名(父类名):
# 子类的属性和方法
4. 继承示例代码
python
# 父类
class Animal:
def __init__(self, name):
self.name = name
def 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()
函数用于调用父类的方法,通常用于在子类中扩展父类的方法。
python
class Animal:
def __init__(self, name, color):
self.name = name
self.color = color
class Bird(Animal):
def __init__(self, name, color, can_fly):
# 调用父类的__init__方法
super().__init__(name, color)
# 添加子类自己的属性
self.can_fly = can_fly
def 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. 单继承的使用场景
- 当多个类有共同的属性和方法时,可以将这些共同部分提取到父类中
- 当需要创建一个更具体的类,它是某个更通用类的特例时
- 当需要扩展现有类的功能,但又不想修改原有类的代码时(开放 - 封闭原则)
三、封装与继承的结合使用
在实际开发中,封装和继承通常是结合使用的。子类可以继承父类的公有和受保护成员,但不能直接访问父类的私有成员。
python
class Person:
def __init__(self, name, age):
self.name = name # 公开属性
self.__age = 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_age
else:
print("Invalid age!")
# 学生类继承自人类
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age)
self.student_id = student_id
def 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)机制,即解释器会将其重命名为_类名__属性名
的形式,以避免子类中的命名冲突。
因此,我们可以通过以下两种方式访问私有成员:
- 通过类内部的公有方法访问(推荐)
这是最规范的方式,通过类内部定义的公开方法(getter/setter)来间接访问或修改私有成员,符合封装的设计思想。
python
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_age
else:
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
- 通过名称修饰后的名称直接访问(不推荐)
利用 Python 的名称修饰规则,我们可以在类外部通过_类名__私有成员名
的形式直接访问私有属性和方法。但这种方式破坏了封装性,不建议在实际开发中使用。
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()
函数调用父类的方法- 子类可以重写父类的方法,实现多态
综合
- 封装和继承通常结合使用
- 子类可以继承父类的公有和受保护成员
- 子类不能直接访问父类的私有成员,需通过父类提供的接口访问