文章目录
前言
以往我们写的代码都是面向过程的即为了解决某一个问题一步一步书写代码,而面向对象编程则是将要解决的问题,进行模块化的分割,把这些模块当作一个个对象,通过完成对象的代码,从而实现解决整个问题, 面向对象就是先画图纸(类),再批量造实物(对象),把东西装盒(封装),复用功能(继承),统一接口(多态)!
一.面向对象概述
1.1 对象vs类
Python 是一种面向对象的编程语言,在 Python 中,一切皆为对象。每个对象都属于某个特定的类,类是创建对象的蓝图或模板,它定义了对象的属性和方法。例如,当我们定义一个Person
类,然后通过这个类创建出具体的person1
、person2
等实例,这些实例就是对象,话句话说,将具有相同属性和方法的对象封装成的集合称为类。
1.1.1 Python 中的对象
- 定义:对象是对现实世界中事物的抽象表示,是类的实例。在 Python 中一切皆为对象,每个对象都有自己的状态(数据)和行为(方法)。
- 属性和方法
- 属性:用于存储对象的状态或数据。例如一个表示汽车的对象,可能有颜色、品牌、型号等属性。
- 方法:定义对象能够执行的动作和执行该动作需要通过哪些方式完成。比如汽车对象可能有启动、加速、刹车等方法。
1.1.2 Python 中的类
- 定义:类是创建对象的蓝图或模板,它定义了对象的属性和方法。是一种抽象的数据类型,用于将具有相同特征和行为的对象进行归类和描述。
- 类的组成
- 属性定义:在类中定义的变量,用于描述类的特征。比如在一个
Student
类中,可以定义name
、age
、grade
等属性来表示学生的姓名、年龄和成绩。 - 方法定义:在类中定义的函数,用于描述类的行为。以
Student
类为例,可能有study()
、take_exam()
等方法来表示学生学习和参加考试的行为。
- 属性定义:在类中定义的变量,用于描述类的特征。比如在一个
把汽车看作一个“类”,这个类像是汽车生产的蓝图,规定了汽车有哪些属性和方法。
类属性就像是工厂的通用信息,是所有汽车都共享的。比如,工厂生产的所有汽车都使用同一种类型的轮胎品牌,这个 “轮胎品牌” 就是类属性。
实例属性是每辆汽车独有的特点,就像辆汽车都有自己的颜色、型号和车牌号。当工厂生产出一辆汽车时,这些属性就会被确定下来,并且不同的汽车这些属性可能不同。
实例方法是每一辆汽车可以执行的特定操作,这些操作和具体的某一辆汽车相关。比如,汽车可以启动、加速、刹车,这些行为就是实例方法。每一辆汽车执行这些操作时,会根据自己的状态(实例属性)产生不同的结果。
类方法就像是工厂的管理操作,和整个汽车类相关,而不是和某一辆具体的汽车相关。比如,工厂决定更换所有汽车使用的轮胎品牌(类属性),这就是一个类方法。
二.类的定义和使用
以下是一个简单的 Python 代码示例,用于展示类和对象的关系及使用:
#创建类car
class Car:
def __init__(self, brand, color):
self.brand = brand
self.color = color
def start(self):
print(f"The {self.color} {self.brand} car is starting.")
# 创建Car类的对象
car1 = Car("BMW", "black")
car2 = Car("Toyota", "white")
# 调用对象的方法
car1.start()
car2.start()
运行结果如下:
2.1 定义类
class Car:
class
是 Python 中用于定义类的关键字。Car
是类的名称,按照 Python 的命名规范,类名通常采用大驼峰命名法,即每个单词的首字母大写。这里定义了一个名为Car
的类,用于描述汽车的相关属性和行为。
2.2 魔法方法
2.2.1 初始化方法__init__
()
在 Python 中,__init__()
是一个特殊的方法,也被称为构造函数,它在类的实例化(也就是创建对象)过程中Python解释器会自动调用,用于初始化对象的属性。
def __init__(self, brand, color):
self.brand = brand
self.color = color
self
是一个约定俗成的参数名,它代表类的实例对象本身。在类的方法中,通过self
可以访问和修改对象的属性,创建实例属性必须在构造函数中使用self关键字创建。brand
和color
是__init__
方法的参数,用于接收创建对象时传入的汽车品牌和颜色信息。self.brand = brand
和self.color = color
这两行代码将传入的brand
和color
参数分别赋值给对象的brand
和color
属性,从而完成对象属性的初始化。
2.2.2 加减法 __add/sub__()
在 Python 中,__add__()
是一个特殊方法(也称为魔法方法),用于定义对象在使用 +
运算符时的行为。当你对两个对象使用 +
运算符时,Python 会尝试调用左操作数的 __add__()
方法来处理这个加法操作。以下是关于 __add__()
方法的详细介绍:
基本语法:
class ClassName:
def __add__(self, other):
# 定义加法操作的逻辑
# self 表示左操作数,other 表示右操作数
# 返回一个新的对象或者计算结果
pass
下面是一个自定义类中实现 __add__()
方法的示例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# 定义两个 Point 对象相加的逻辑
new_x = self.x + other.x
new_y = self.y + other.y
return Point(new_x, new_y)
def __str__(self):
return f"Point({self.x}, {self.y})"
# 创建 Point 对象
p1 = Point(1, 2)
p2 = Point(3, 4)
# 使用 + 运算符
p3 = p1 + p2
print(p3)
运行结果如下:
在这个示例中:
- 定义了一个
Point
类,用于表示二维平面上的点,包含x
和y
坐标。 __add__()
方法定义了两个Point
对象相加的逻辑,即将它们的x
坐标和y
坐标分别相加,然后返回一个新的Point
对象。__str__()
方法用于将Point
对象转换为字符串表示,方便打印输出。- 创建了两个
Point
对象p1
和p2
,使用+
运算符将它们相加,得到一个新的Point
对象p3
,最后打印输出p3
的信息。
在 Python 中,__sub__()
是一个特殊方法(也被称为魔术方法或双下划线方法),它用于实现减法运算符 -
的行为。当你使用 -
运算符对两个对象进行减法操作时,Python 会尝试调用对象的 __sub__()
方法来完成这个操作。
基本语法:
class ClassName:
def __sub__(self, other):
# 实现减法逻辑
# self 表示运算符左边的对象
# other 表示运算符右边的对象
# 返回减法操作的结果
pass
参数说明
self
:代表调用该方法的对象,也就是减法运算符左边的对象。other
:代表减法运算符右边的对象。
下面是一个__sub__()方法的示例案例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __sub__(self, other):
# 实现 Point 对象的减法操作
new_x = self.x - other.x
new_y = self.y - other.y
return Point(new_x, new_y)
def __str__(self):
# 定义对象的字符串表示形式
return f"Point({self.x}, {self.y})"
# 创建两个 Point 对象
p1 = Point(5, 3)
p2 = Point(2, 1)
# 使用减法运算符
result = p1 - p2
print(result) # 输出: Point(3, 2)
2.2.3 比较方法__eql__()
- 作用:定义对象的相等比较行为,即判断两个对象是否相等,当使用
==
运算符时会调用该方法。 - 示例:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
p1 = Person("Alice", 25)
p2 = Person("Alice", 25)
print(p1 == p2) # 输出: True
2.2.4 字符串方法__str__()
__str__(self)
- 作用:返回对象的字符串表示形式,通常用于用户友好的输出,当使用
print()
函数或str()
函数时会调用该方法。
- 作用:返回对象的字符串表示形式,通常用于用户友好的输出,当使用
基本语法:
class ClassName:
def __str__(self):
# 这里实现将对象转换为字符串的逻辑
# 通常返回一个描述对象的字符串
return "描述对象的字符串"
案例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(1, 2)
print(p) # 输出: Point(1, 2)
# 使用 str 函数将对象转换为字符串
str_p = str(p)
print(str_p) #输出Point(1,2)
2.3 类属性
在 Python 中,类属性是属于类本身的属性,而不是属于类的某个实例。类属性被该类的所有实例共享,以下是关于 Python 类属性的详细介绍:
2.3.1 定义类属性
class ClassName:
# 定义类属性
class_attribute = value
2.3.2 类属性的访问
可以通过类名或类的实例来访问类属性:
# 通过类名访问类属性
print(Dog.species) # 输出: Canis familiaris
# 通过实例访问类属性
print(dog1.species) # 输出: Canis familiaris
2.3.3 修改类属性
# 修改类属性
Dog.species = 'Canis lupus familiaris'
# 通过类名访问类属性
print(Dog.species) # 输出: Canis lupus familiaris
# 通过实例访问类属性
print(dog1.species) # 输出: Canis lupus familiaris
print(dog2.species) # 输出: Canis lupus familiaris
可以通过类名来修改类属性的值,修改后,所有实例访问该类属性时都会得到新的值:
下面是一个简单的示例:
在上述代码中,species
是 Dog
类的类属性,它被所有 Dog
类的实例共享。
class Dog:
# 定义类属性
species = 'Canis familiaris'
def __init__(self, name, age):
self.name = name
self.age = age
# 创建Dog类的实例
dog1 = Dog('Buddy', 3)
dog2 = Dog('Max', 5)
# 访问类属性
print(dog1.species) # 输出: Canis familiaris
print(dog2.species) # 输出: Canis familiaris
2.4 类方法
2.4.1 定义类方法
类方法的定义需要使用 @classmethod
装饰器,在类方法里,cls
代表定义该方法的类。这就使得在类方法内部能够访问和操作类的属性与方法。其基本语法如下:
class ClassName:
@classmethod
def class_method(cls, *args):
# 方法体
pass
下面是一个简单的示例:
class MyClass:
class_attribute = 10
@classmethod
def class_method(cls):
return cls.class_attribute
# 调用类方法
result = MyClass.class_method()
print(result) # 输出: 10
三.面向对象三大基本特征
3.1 封装
封装是 Python 面向对象编程的三大基本特征之一,它是将对象的属性和方法封装起来,并对外部隐藏对象的内部实现细节,仅提供必要的接口供外部访问和操作。以下从封装的概念、实现方式、优点等方面进行详细介绍:
实现封装方式
在 Python 中,虽然没有像其他一些编程语言(如 Java)那样严格的访问控制修饰符,但可以通过命名约定来实现一定程度的封装:
- 公有属性和方法:没有任何特殊前缀的属性和方法是公有的,可以在类的外部直接访问。
- 私有属性和方法:在属性或方法名前加上双下划线
__
来表示私有,私有属性和方法只能在类的内部访问。 - 受保护属性:在属性名前加一个下划线 _ 来表示这个属性是受保护的。它只是提醒开发者这个属性不应该被外部直接访问,应该通过类的方法来访问,而非在类的外部强行访问,受保护属性可以被继承,而私有属性无法被继承。
未实例封装前:
# 未使用封装时的简单账户表示
account_owner = "Alice"
account_balance = 1000
# 取款操作
def withdraw_money(amount):
global account_balance
if amount > account_balance:
print("余额不足,无法取款。")
else:
account_balance -= amount
print(f"成功取款 {amount} 元,当前余额为 {account_balance} 元。")
# 存款操作
def deposit_money(amount):
global account_balance
account_balance += amount
print(f"成功存款 {amount} 元,当前余额为 {account_balance} 元。")
直接修改账户余额(可能导致不合理情况)
account_balance = -100
print(f"账户余额被直接修改为 {account_balance} 元。")
在未使用封装的代码中,账户信息(如 account_owner
和 account_balance
)以全局变量的形式存在,这使得这些数据可以被代码中的任何部分直接访问和修改。例如,可以直接将 account_balance
修改为负数,这显然不符合实际的银行账户逻辑,会导致数据的安全性和完整性受到严重威胁。
使用封装后:
class BankAccount:
def __init__(self, owner, initial_balance):
# 私有属性,外部无法直接访问
self.__owner = owner
self.__balance = initial_balance
# 存款方法
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"成功存款 {amount} 元,当前余额为 {self.__balance} 元。")
else:
print("存款金额必须为正数。")
# 取款方法
def withdraw(self, amount):
if amount > 0:
if amount <= self.__balance:
self.__balance -= amount
print(f"成功取款 {amount} 元,当前余额为 {self.__balance} 元。")
else:
print("余额不足,无法取款。")
else:
print("取款金额必须为正数。")
# 获取账户余额的方法
def get_balance(self):
return self.__balance
# 获取账户所有者的方法
def get_owner(self):
return self.__owner
# 创建一个银行账户实例
account = BankAccount("Alice", 1000)
# 进行存款操作
account.deposit(500)
# 进行取款操作
account.withdraw(300)
# 尝试直接访问私有属性(会报错)
# print(account.__balance)
# 通过公共方法获取账户余额
print(f"当前账户余额为 {account.get_balance()} 元。")
在封装后的 BankAccount 类中,__owner 和 __balance 被定义为私有属性(通过在属性名前加双下划线 __ 实现)。外部代码无法直接访问和修改这些私有属性,只能通过类提供的公共方法(如 deposit、withdraw 等即对外开放的接口)且满足相应条件(amount>0)后才能操作数据。这样就避免了外部代码对数据的非法修改,保证了账户数据的安全性。
3.2 继承
Python里的类继承允许一个类(子类)继承另一个类(父类)的属性和方法。这样可以实现代码重用,避免重复编写相同的代码。子类可以扩展或修改父类的功能。
语法:
在Python中,子类的定义是在类名后面加上父类名,比如class Dog(Animal):。这样Dog就继承了Animal的所有内容。
class Human:
# 类Human的属性和方法
class Student(Human):
使用场景:
- 当多个类有一些共同的属性和方法时,可以将这些共同的部分提取到一个父类中,然后让其他类继承这个父类,从而避免代码的重复编写。
- 子类可以在继承父类的基础上添加新的属性和方法,或重写父类的方法,以实现功能的扩展。
特点:
- 子类可以直接继承父类的公有属性和方法,私有属性无法继承,但可以通过父类的公有方法访问父类的私有属性。
- 示例:
class Vehicle: def move(self): print("正在移动...") class Car(Vehicle): # 继承Vehicle pass car = Car() car.move() # 直接复用父类方法,输出"正在移动..."
- 子类使用super()重写父类的方法从而方便子类在此基础上扩展出新功能,其次若不使用
super()
:父类的__init__
方法不会执行,导致属性未初始化。 - 示例:
class Animal: def __init__(self, name): self.name = name def speak(self): return "声音!" class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # 调用父类初始化 self.breed = breed def speak(self): # 覆盖父类方法 return "汪汪!"
3.3 多态
将父类对象应用于子类的特征就是多态。比如创建一个动物类,动物类都有“发出声音”的方法,然后再创建两个子类,一个是狗类,一个是猫类,它们都继承了动物类,都具有“发出声音“的属性,但是狗类发出的声音是“旺旺!”,猫类发出的声音是“喵喵!”,而不同子类调用相同的父类方法,执行出不同结果的现象称为多态。
案例分析:
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("Woof!")
class Cat(Animal):
def speak(self):
print("Miao")
def animal_speak(animal:Animal):
animal.speak()
dog = Dog()
dog.speak()
cat = Cat()
cat.speak()
四.异常处理
在 Python 中,异常处理是一种机制,用于处理程序运行时可能出现的错误或异常情况,以确保程序在遇到错误时不会崩溃,而是能够按照开发者预先设定的方式进行处理,从而增强程序的健壮性和稳定性。下面从异常的概念、异常处理的基本语法、异常处理流程、常见异常类型、异常处理的高级用法等方面详细介绍。
异常处理概念
在程序运行过程中,由于各种原因(如输入错误、文件不存在、网络连接失败等)可能会导致程序无法正常执行,这时就会抛出异常。例如,当你试图将一个字符串和一个整数相加时,Python 会抛出 TypeError
异常。
result = "hello" + 1 # 会抛出 TypeError 异常
4.1 异常处理基本语法
try:
# 可能会抛出异常的代码块
pass
except ExceptionType1:
# 处理 ExceptionType1 类型异常的代码块
pass
except ExceptionType2:
# 处理 ExceptionType2 类型异常的代码块
pass
else:
# 当 try 代码块没有抛出任何异常时执行的代码块
pass
finally:
# 无论 try 代码块是否抛出异常,都会执行的代码块
pass
4.2 异常处理执行流程
- 执行
try
代码块:程序首先执行try
代码块中的语句。 - 捕获异常:如果
try
代码块中的语句抛出异常,Python 会停止执行try
代码块中剩余的语句,然后查找与之匹配的except
子句。 - 执行
except
代码块:如果找到匹配的except
子句,就会执行该子句中的代码;如果没有找到匹配的except
子句,异常会继续向上抛出。 - 执行
else
代码块:如果try
代码块中没有抛出任何异常,就会执行else
代码块中的语句。 - 执行
finally
代码块:无论try
代码块是否抛出异常,finally
代码块中的语句都会被执行。
示例代码
try:
num1 = int(input("请输入一个整数: "))
num2 = int(input("请输入另一个整数: "))
result = num1 / num2
except ValueError:
print("输入的不是有效的整数,请重新输入。")
except ZeroDivisionError:
print("除数不能为零,请重新输入。")
else:
print(f"结果是: {result}")
finally:
print("程序执行结束。")
4.3 常见异常类型
SyntaxError:语法错误,通常是由于代码书写不符合 Python 语法规则导致的。
IndentationError:缩进错误,Python 使用缩进来表示代码块,如果缩进不正确就会抛出该异常。
NameError:名称错误,当使用一个未定义的变量或函数时会抛出该异常。
TypeError:类型错误,当操作或函数应用于不适当类型的对象时会抛出该异常。
ValueError:值错误,当函数接收到一个类型正确但值不合适的参数时会抛出该异常。
ZeroDivisionError:除零错误,当试图用一个数除以零时会抛出该异常。
4.4 异常处理的高基用法
捕获多个异常:
可以在一个 except
子句中捕获多个异常,将异常类型用元组括起来。
try:
# 可能会抛出异常的代码
pass
except (ExceptionType1, ExceptionType2):
# 处理 ExceptionType1 或 ExceptionType2 类型异常的代码
pass
抛出异常:
在 Python 中,可以使用 raise
语句手动抛出异常。
def divide(num1, num2):
if num2 == 0:
raise ZeroDivisionError("除数不能为零。")
return num1 / num2
try:
result = divide(10, 0)
except ZeroDivisionError as e:
print(e)
自定义异常:
自定义异常类会继承自 Exception
类,因为 Exception
是大多数内置异常的基类。通过继承 Exception
类,你可以创建自己的异常类型,并在程序中抛出和捕获这些异常。
自定义异常的步骤和示例
1. 定义自定义异常类
要定义自定义异常类,只需创建一个新的类,并让它继承自 Exception
类。以下是一个简单的示例:
# 定义一个自定义异常类,继承自 Exception
class MyCustomError(Exception):
pass
在这个示例中,我们定义了一个名为 MyCustomError
的自定义异常类,它继承自 Exception
类。目前这个类没有添加任何额外的功能,只是一个简单的自定义异常类型。
2. 在程序中抛出自定义异常
定义好自定义异常类后,就可以在程序中使用 raise
语句抛出这个异常。以下是一个示例:
class MyCustomError(Exception):
pass
def check_number(num):
if num < 0:
# 当 num 小于 0 时,抛出自定义异常
raise MyCustomError("输入的数字不能为负数。")
return num
try:
result = check_number(-5)
except MyCustomError as e:
print(f"捕获到自定义异常: {e}")
在这个示例中,check_number
函数用于检查输入的数字是否为负数。如果输入的数字小于 0,则抛出 MyCustomError
异常,并附带一条错误信息。在 try
块中调用 check_number
函数,当抛出异常时,except
块会捕获该异常,并打印出错误信息。
3. 为自定义异常类添加额外功能
你可以为自定义异常类添加额外的属性和方法,以满足更复杂的需求。例如,为异常类添加一个初始化方法,用于传递更多的错误信息:
class MyCustomError(Exception):
def __init__(self, num, message="输入的数字不能为负数。"):
self.num = num
self.message = message
super().__init__(self.message)
def __str__(self):
return f"{self.message} 输入的数字是: {self.num}"
def check_number(num):
if num < 0:
raise MyCustomError(num)
return num
try:
result = check_number(-5)
except MyCustomError as e:
print(f"捕获到自定义异常: {e}")
在这个示例中,我们为 MyCustomError
类添加了一个 __init__
方法,用于接收输入的数字和错误信息。同时,重写了 __str__
方法,以便在打印异常信息时,能输出更详细的内容。
案例分析:
下面是通过异常处理来遍历文件夹找到对应文件的案例,通过异常处理我们可以在文件找到的第一时间中断程序,从而减少程序运行的时间。
案例路径下文件中的文件如下:
程序未使用异常处理前:
import os
class FindFile(Exception):
pass
def search_file(path,filename):
file_ls = os.listdir(path)
for file in file_ls:
abs_file = os.path.join(path,file)
print(abs_file)
if os.path.isfile(abs_file) and file == filename:
print("文件找到了")
elif os.path.isdir(abs_file):
search_file(abs_file,filename)
search_file(r"F:\python\python代码\网络工程师的python之路\测试文件","文件IO流.py")
运行结果:
程序使用异常处理后:
import os
class FindFile(Exception):
pass
def search_file(path,filename):
file_ls = os.listdir(path)
for file in file_ls:
abs_file = os.path.join(path,file)
print(abs_file)
if os.path.isfile(abs_file) and file == filename:
raise FindFile("目标已找到")
elif os.path.isdir(abs_file):
search_file(abs_file,filename)
try:
search_file(r"F:\python\python代码\网络工程师的python之路\测试文件","面向对象.py")
except FindFile as f:
print(f"找到文件:{f}了")
运行结果:
结果分析:
在程序未使用异常处理前,即使目标文件已经找到了,由于for 循环的遍历未结束,因此程序不会中断,会继续执行直到for 循环遍历所有文件后程序结束,当我们查找的文件夹下有大量文件时,程序运行时间过长导致CPU和内存占用量过大。
在程序使用异常处理后,即使for 循环未结束,只要找到目标文件,程序就会抛出异常并结束程序从而提升效率。
代码详细分析:
1. 自定义异常类 FindFile
的定义
class FindFile(Exception):
pass
这里定义了一个名为 FindFile
的自定义异常类,它继承自 Python 内置的 Exception
类。通过继承 Exception
类,FindFile
具备了作为异常类型的基本特性,可以被抛出和捕获。
2. 文件搜索函数 search_file
def search_file(path, filename):
file_ls = os.listdir(path)
for file in file_ls:
abs_file = os.path.join(path, file)
if os.path.isfile(abs_file) and file == filename:
raise FindFile("目标已找到")
elif os.path.isdir(abs_file):
search_file(abs_file, filename)
- os.listdir(path):获取指定目录 path 下的所有文件和文件夹列表。
- 遍历这个列表,对于每个元素,使用 os.path.join(path, file) 拼接出完整的绝对路径 abs_file。
- 检查 abs_file 是否为文件,并且文件名是否与要搜索的 filename 相等。如果满足条件,使用 raise FindFile("目标已找到") 抛出 FindFile 异常,并将 "目标已找到" 作为参数传递给异常类的构造函数。
- 如果 abs_file 是一个目录,则递归调用 search_file 函数,继续在该子目录中搜索目标文件
3. try-except
语句块
try:
search_file(r"F:\python\python代码\网络工程师的python之路\测试文件", "文件IO流.py")
except FindFile as f:
print(f"找到文件:{f}了")
- 在
try
语句块中调用search_file
函数进行文件搜索。 - 如果在搜索过程中找到了目标文件,
search_file
函数会抛出FindFile
异常,程序的控制权会立即转移到except
语句块。 except FindFile as f
捕获FindFile
类型的异常,并将异常对象赋值给变量f
。由于FindFile
类继承自Exception
类,当打印f
时,实际上会调用Exception
类的__str__
方法,该方法会返回传递给异常类构造函数的参数,也就是"目标已找到"
。所以最终会输出找到文件:目标已找到
。
综上所述,结果中的 f
就是给类 FindFile
传递的参数 "目标已找到"
,这是由于异常对象在被打印时会调用其继承自 Exception
类的 __str__
方法,返回构造函数中传入的参数。