面向对象编程
Python 的面向对象编程(Object-Oriented Programming, OOP)是一种编程风格,它将数据(属性)和功能(方法)封装在称为类(class)的结构中。这样做的主要目的是为了提高代码的可重用性、可维护性和可扩展性。
在 Python 中,面向对象编程主要包含以下几个核心概念:
- 类(Class): 类是对象的蓝图或模板,定义了对象的属性(数据)和方法(功能)。
- 对象(Object): 类的实例化,是一个具有类定义的属性和方法的具体实体。
- 继承(Inheritance): 子类继承父类的属性和方法,可以扩展或重写父类的功能。
- 多态(Polymorphism): 不同类的对象对同一消息做出不同的响应。
- 封装(Encapsulation): 将数据和方法绑定在一起,并通过访问控制保护对象的内部状态。
类与对象
类是一个蓝图或模板,用于定义对象的属性(数据)和方法(行为)。通过类,你可以创建具有相同属性和行为的多个对象。
类的定义
在Python中,使用 class 关键字定义一个类。类通常包含两个主要部分:
- 属性:类中的变量,通常用于存储对象的状态或数据。
- 方法:类中的函数,通常用于定义对象的行为或操作
对象是类的实例化,即根据类创建的具体实体。每个对象都有自己独立的属性值,但共享同一个类定义的结构和方法
类的属性与方法
class Dog:
# 类属性
species = "Canis familiaris"
# 初始化方法,创建实例时自动调用
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 实例方法
def bark(self):
return f"{self.name} 说:汪汪!"
# 实例方法
def get_age(self):
return f"{self.name} 是 {self.age} 岁。"
在这个例子中,Dog 类定义了一个狗的模板,包含了属性 name 和 age,以及方法 bark 和 get_age。
进一步理解
- 封装:类将数据和操作封装在一起,使得代码更清晰、更易维护。
- 继承:可以创建一个新类,它继承(复用)已有类的属性和方法,同时可以扩展或修改。
- 多态:对象可以通过同样的接口调用不同类的实现,从而实现灵活的代码设计。
案例理解
创建一个简单的银行账户系统,这个系统允许用户创建账户、存款、取款,并查询余额
定义一个 BankAccount 类,这个类将作为银行账户的模板。它将包含账户持有人的名字、账户余额,以及一些用于操作账户的功能(如存款和取款)
class BankAccount:
# 初始化方法,用于创建新账户时设定初始值
def __init__(self, owner, balance=0):
self.owner = owner # 账户持有人
self.balance = balance # 账户余额,默认为0
# 方法:显示账户信息
def display_account_info(self):
return f"账户持有人: {self.owner}, 账户余额: {self.balance} 元"
# 方法:存款
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"{amount} 元已存入账户。新余额: {self.balance} 元")
else:
print("存款金额必须大于0元。")
# 方法:取款
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
print(f"{amount} 元已从账户中取出。新余额: {self.balance} 元")
else:
print("取款金额无效或余额不足。")
使用 BankAccount 类来创建两个银行账户对象,并对其进行操作
# 创建两个银行账户对象
account_1 = BankAccount("张三", 1000)
account_2 = BankAccount("李四", 500)
# 显示账户信息
print(account_1.display_account_info()) # 输出: 账户持有人: 张三, 账户余额: 1000 元
print(account_2.display_account_info()) # 输出: 账户持有人: 李四, 账户余额: 500 元
# 对账户进行存款和取款操作
account_1.deposit(500) # 张三存款500元
account_1.withdraw(300) # 张三取款300元
account_2.deposit(200) # 李四存款200元
account_2.withdraw(800) # 李四试图取款800元,失败因为余额不足
运行结果
账户持有人: 张三, 账户余额: 1000 元
账户持有人: 李四, 账户余额: 500 元
500 元已存入账户。新余额: 1500 元
300 元已从账户中取出。新余额: 1200 元
200 元已存入账户。新余额: 700 元
取款金额无效或余额不足。
构造函数与析构函数
在Python中,构造函数和析构函数是与类(Class)相关的特殊方法,用于在对象创建和销毁时执行特定操作。在对象的生命周期内管理资源和初始化属性。
构造函数(Constructor)
构造函数是在创建对象时自动调用的方法,用于初始化对象的状态或属性。在Python中,构造函数由一个特殊的方法 init() 表示。
特点:
- 构造函数的主要作用是设置对象的初始状态,即为对象的属性赋初值。
- 当你创建一个类的实例时,Python会自动调用 init() 方法,不需要你显式调用它。
class Person:
# 构造函数
def __init__(self, name, age):
self.name = name # 初始化name属性
self.age = age # 初始化age属性
def greet(self):
print(f"大家好,我是 {self.name},今年 {self.age} 岁。")
# 创建对象时自动调用构造函数
person_1 = Person("张三", 30)
person_1.greet() # 输出: 大家好,我是 张三,今年 30 岁。
在这个例子中,init() 是 Person 类的构造函数,用于在创建 Person 对象时初始化 name 和 age 属性
析构函数(Destructor)
析构函数是在对象被销毁(即不再被使用)时自动调用的方法,用于清理资源或执行其他必要的清理操作。在Python中,析构函数由一个特殊的方法 del() 表示。
特点:
- 析构函数的主要作用是在对象生命周期结束时执行清理操作,如关闭文件、释放资源等。
- 当一个对象不再被引用(即它的引用计数变为0)时,Python的垃圾收集器会调用 del() 方法销毁对象。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"{self.name} 对象已创建")
def greet(self):
print(f"大家好,我是 {self.name},今年 {self.age} 岁。")
# 析构函数
def __del__(self):
print(f"{self.name} 对象已销毁")
# 创建对象
person_1 = Person("张三", 30)
person_1.greet() # 输出: 大家好,我是 张三,今年 30 岁。
# 删除对象
del person_1 # 手动销毁对象,调用析构函数
在这个例子中,del() 是 Person 类的析构函数,用于在对象被销毁时输出一条消息。你可以手动销毁对象,也可以让Python的垃圾回收器自动处理。
总结
- 构造函数 (init):在对象创建时自动调用,用于初始化对象的属性和状态。
- 析构函数 (del):在对象销毁时自动调用,用于清理资源或执行其他必要的清理操作。
继承与多态
继承(Inheritance)
继承允许你创建一个新的类,这个类可以继承(复用)另一个类的属性和方法。被继承的类称为父类或基类,新创建的类称为子类或派生类。
继承的特点:
- 代码重用:子类可以直接使用父类中定义的属性和方法,而无需重新编写。
- 扩展性:子类可以添加新的属性和方法,或者重写父类的方法来实现不同的行为。
# 定义父类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} 发出声音。"
# 定义子类,继承自 Animal
class Dog(Animal):
def __init__(self, name, breed):
# 调用父类的构造函数
super().__init__(name)
self.breed = breed
# 重写父类的 speak 方法
def speak(self):
return f"{self.name} 说:汪汪!"
# 定义另一个子类
class Cat(Animal):
def speak(self):
return f"{self.name} 说:喵喵!"
# 创建对象
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")
print(dog.speak()) # 输出: Buddy 说:汪汪!
print(cat.speak()) # 输出: Whiskers 说:喵喵!
多态(Polymorphism)
多态指的是“同一接口,不同实现”的能力。换句话说,不同的子类可以以各自特定的方式实现父类的相同方法。当你通过父类引用调用这个方法时,它会自动选择正确的子类实现。
多态的特点:
- 接口统一:多态使得子类可以通过相同的接口(方法名)实现不同的行为。
- 灵活性和扩展性:通过多态,程序可以在不修改代码的前提下,轻松扩展新的子类和新的行为。
# 使用前面的类定义
# 定义一个函数,接受任意 Animal 对象,并调用 speak 方法
def animal_sound(animal):
print(animal.speak())
# 调用函数时,传入不同的子类对象
animal_sound(dog) # 输出: Buddy 说:汪汪!
animal_sound(cat) # 输出: Whiskers 说:喵喵!
继承与多态的结合
继承和多态通常是结合使用的。继承提供了代码复用的机制,而多态提供了扩展和灵活性。
封装与私有属性
封装和私有属性是面向对象编程中的两个重要概念,它们帮助你保护对象的内部状态,并控制对其的访问。
封装(Encapsulation)
*封装是指将对象的属性(数据)和方法(行为)封装在一起,使它们成为一个独立的单元。在 Python中,通过封装,你可以隐藏对象的内部实现细节,暴露出简洁的接口(方法),供外部使用。
封装的特点:
- 数据保护:通过封装,你可以保护对象的内部状态,不让外部随意修改。
- 简化接口:只向外部暴露必要的方法,隐藏复杂的内部实现。
- 提高代码维护性:内部实现的变化不会影响外部代码。
class Employee:
def __init__(self, name, salary):
self.name = name # 公有属性
self.__salary = salary # 私有属性(用两个下划线前缀)
# 公有方法,外部可访问
def get_info(self):
return f"员工姓名: {self.name}, 薪水: {self.__salary} 元"
# 私有方法,外部不可直接访问
def __calculate_tax(self):
return self.__salary * 0.2
# 提供一个公有方法访问私有方法
def get_tax(self):
return f"{self.name} 的应缴税款: {self.__calculate_tax()} 元"
在这个例子中,Employee 类封装了员工的姓名和薪水,并提供了公有方法 get_info() 和 get_tax(),供外部访问。这些方法隐藏了内部计算税款的逻辑,只暴露必要的信息
私有属性(Private Attributes)
私有属性是指只能在类的内部访问的属性。在Python中,使用两个下划线前缀(如 __attribute)将属性声明为私有。私有属性不能在类外部直接访问,这样可以防止外部代码意外或恶意地修改对象的内部状态。
私有属性的特点:
- 只能在类内部访问:私有属性不能在类外部直接访问或修改。
- 保护对象的内部状态:防止外部代码破坏对象的一致性或安全性。
# 使用前面的 Employee 类定义
# 创建对象
emp = Employee("李四", 5000)
# 访问公有属性
print(emp.name) # 输出: 李四
# 试图访问私有属性(会导致错误)
# print(emp.__salary) # 这行代码会报错:AttributeError
# 正确访问私有属性的方法是通过类的公有方法
print(emp.get_info()) # 输出: 员工姓名: 李四, 薪水: 5000 元
# 访问私有方法计算税款
print(emp.get_tax()) # 输出: 李四 的应缴税款: 1000.0 元
在这个例子中,__salary 是私有属性,无法在类外部直接访问,但可以通过公有方法 get_info() 和 get_tax() 来间接访问和操作
小结
- 封装是一种将对象的属性和方法封装在一起的技术,通过限制对某些属性和方法的访问,保护对象的内部状态,简化外部接口。
- 私有属性是封装的实现手段之一,使用两个下划线前缀将属性设为私有,防止外部直接访问。
使用封装和私有属性的好处:
- 安全性:保护对象的内部数据,防止被外部代码篡改。
灵活性:可以随时修改对象的内部实现,而不影响外部代码。
易于维护:通过清晰的接口,减少外部代码与内部实现的耦合,使得代码更易维护和扩展