8.类与对象
8.1 面向对象
面向对象的三大基本特征: 封装、继承、多态。
在面向对象编程中,封装(Encapsulation)是一种将数据和操作(方法)组合在一起的机制。通过封装,我们可以隐藏数据的具体实现细节,只暴露出一些对外的接口,外部程序只能通过这些接口来访问和操作数据。
封装的主要目的是保护数据的安全性和完整性,防止外部程序直接修改数据,导致不可预料的错误。同时,封装也提供了简化操作的便利性,对外部程序来说,只需要关注公开的接口,而不需要关心具体的实现方式。
在封装中,通常会将数据声明为私有的(private),并提供一些公开的方法(public methods)来获取和修改数据。这些公开的方法可以对数据进行一些校验和处理,保证数据的有效性和一致性。
封装也有助于实现代码的可维护性和可扩展性,通过封装,我们可以将修改数据和操作的逻辑集中在一个地方,方便后续的修改和扩展。
总结来说,封装是一种将数据和操作组合在一起的机制,通过封装可以隐藏数据的实现细节,保护数据的安全性,提供简化操作的接口,并提高代码的可维护性和可扩展性。
继承是面向对象编程中的一个重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类、基类或超类)继承属性和方法。
子类可以继承父类的属性和方法,并且还可以添加自己的特定属性和方法。这样可以实现代码的重用和扩展。
多态是面向对象编程中的一个概念,指的是同一个方法在不同对象上的不同表现形式。简单说,就是通过使用父类的引用指向子类的对象,实现对不同子类对象调用同一个方法时产生不同的结果。
8.2 类与对象
8.2.1 类与对象的关系
类与对象的关系是一种包含与被包含的关系。
类是对一类具有相同属性和行为的对象的抽象描述,它定义了对象的属性和方法。它可以看作是一个模板或蓝图,描述了对象应该具有的特征和行为。
对象是类的实例化,是具体存在的实体,它具有类定义的属性和方法。
类和对象的关系可以用类比现实生活中的一对模具和铸件的关系。类就像是一个模具,定义了对象的特征和行为;对象就像是具体的铸件,是根据模具创建出来的实体。
类与对象的关系还可以用包含与被包含的关系来理解。一个类可以包含多个对象,这些对象都是类的实例化。而一个对象则包含类定义的属性和方法。
总结来说,类是对一类对象的抽象描述,对象是类的实例化,它们之间是一种包含与被包含的关系。
8.2.2 类的定义与访问
class 类名:
属性名=属性值
def 方法名(self):
方法体
>>> class Car:wheels=4def drive(self):print('开车方法')def stop(self):print('停车方法')>>>
在Python中,self是一个特殊的参数,它代表当前对象实例本身。通过使用self,我们可以在类的方法中引用和操作对象的属性和方法。
在类的方法中,通常第一个参数会被命名为self,用于接收当前对象的引用。当我们调用对象的方法时,Python会自动将对象本身作为第一个参数传递给self。
使用self可以让类的方法操作对象的属性和方法。通过self,我们可以访问和修改对象的属性,调用对象的其他方法,以及在方法中创建临时变量。
实际上,self并不是Python中固有的关键字,我们可以用其他名称来代替self,但约定俗成的做法是使用self。在方法的定义和调用中,都应遵循这个约定,以保持代码的可读性和一致性。
例如,下面是一个简单的示例,演示了如何在类的方法中使用self:
class Person:def __init__(self, name, age):self.name = nameself.age = agedef say_hello(self):print(f"Hello, my name is {self.name} and I am {self.age} years old.")person1 = Person("Alice", 25)
person1.say_hello() # 输出: Hello, my name is Alice and I am 25 years old.
在上面的例子中,self被用于访问对象的name和age属性。当调用person1对象的say_hello方法时,self会自动接收person1对象的引用,并以此访问和操作对象的属性。
8.2.3 对象的创建与使用
①对象的创建
对象名=类名()
my_car=Car
②访问对象成员
对象名.属性
对象名.方法()
class Car:wheels=4def drive(self):print('开车方法')def stop(self):print('停车方法')my_car=Car() #创建对象
print(my_car.wheels)#访问对象的成员属性
my_car.drive() #访问对象的成员方法
8.2.4 访问限制
①定义私有成员
__属性名
__方法名
②私有成员的访问
>>> class PersonInfo:__weight=55def __info(self):print(f'我的体重是:{__weight}')>>> person=PersonInfo()
>>> person.__weight #类外不访问私有成员,会报错
Traceback (most recent call last):File "<pyshell>", line 1, in <module>
AttributeError: 'PersonInfo' object has no attribute '__weight'
>>> person.__info()
Traceback (most recent call last):File "<pyshell>", line 1, in <module>
AttributeError: 'PersonInfo' object has no attribute '__info'
>>>
(1) 访问私有属性
可以通过访问公有方法间接访问私有属性
>>> class PersonInfo:__weight=55def get_weight(self):print(f'我的体重是:{self.__weight}kg')>>> person=PersonInfo()
>>> person.get_weight()
我的体重是:55kg
(2) 访问私有方法
可以通过访问公有方法间接访问私有方法
>>> class PersonInfo:__weight=55def __info(self):print(f'体重是:{self.__weight}')def get_weight(self):print(f'我的体重是:{self.__weight}kg')self.__info()>>> person=PersonInfo()
>>> person.get_weight()
我的体重是:55kg
体重是:55
>>>
8.3 构造方法与析构方法
类中有两个特殊的方法: 构造方法 __init__()和析构方法 __del__()
8.3.1 构造方法
>>> class Inforamtion(object):def __init__(self,name,sex):self.name=nameself.sex=sexdef info(self):print(f'姓名:{self.name}')print(f'性别:{self.sex}')>>> infomat=Inforamtion('小王','女')
>>> infomat.info()
姓名:小王
性别:女
8.3.2 析构方法
析构方法(Destructor)是一种特殊类型的方法,用于在对象被销毁之前执行清理操作。在许多编程语言中,每个类都可以定义一个析构方法,该方法没有任何参数,并且在对象被销毁时自动调用。
析构方法通常用于释放对象占用的资源,比如关闭文件、释放内存等。当对象不再被引用或程序结束时,析构方法会被自动调用。在调用析构方法之后,对象的内存空间将被回收。
需要注意的是,有些编程语言不支持析构方法,而是通过垃圾回收机制来自动处理内存释放。在这种情况下,开发人员无需显式定义析构方法,而是让垃圾回收机制自动回收不再使用的对象。
在创建自定义类时,如果需要执行一些清理操作(如关闭连接、释放资源等),可以考虑使用析构方法来实现。一般来说,析构方法应该与构造方法相对应,以确保对象创建和销毁时的一致性和完整性。
import sys
class Destruction:def __init__(self):print('对象被创建')def __del__(self):print('对象被释放')des=Destruction()
print(sys.getrefcount(des))
del(des)
打印出 "des" 实例的引用计数。请注意,由于 sys.getrefcount()
会在使用期间增加一个额外的引用计数,因此打印结果将比预期的引用计数高 1。
8.4 类方法和静态方法
Python中的类除了self修饰的普通方法,还可定义使用@classmethon修饰的类方法和使用@staticmethon修饰的静态方法。
8.4.1 类方法
(1) 类方法使用装饰器@classmethod 修饰。
(2) 类方法的第一个参数为 cls 而非 self,它代表类本身
(3) 类方法即可由对象调用,亦可直接由类调用。
(4) 类方法可以修改类属性,实例方法无法修改类属性。
①定义类方法
类名.类方法
对象名.类方法
②修改类属性
>>> class Test:@classmethoddef use_classmet(cls):print('我是类方法')>>> test=Test()
>>> test.use_classmet() #类方法即可由对象调用,亦可直接由类调用。
我是类方法
>>> Test.use_classmet()
我是类方法>>> class Apple():count=0def add_one(self):self.count=1@classmethoddef add_two(cls):cls.count=2>>> apple=Apple()
>>> apple.add_one() #实例方法无法修改类属性,但可以修改实例化对象的属性
>>> print(Apple.count)
0
>>> print(apple.count)
1
>>> apple.add_two() #类方法可以修改类属性,但不可以修改实例化对象的属性
>>> print(Apple.count)
2
8.4.2 静态方法
(1) 静态方法没有 self 参数,它需要使用@staticmethod 修饰
(2) 静态方法中需要以“类名.方法/属性名”的形式访问类的成员
(3) 静态方法即可由对象调用,亦可直接由类调用。
>>> class Example():num=10@staticmethoddef static_method():print(f'类属性的值为:{Example.num}')print('----静态方法')>>> exp=Example()
>>> exp.static_method()
类属性的值为:10
----静态方法
>>> Example.static_method()
类属性的值为:10
----静态方法
8.5 继承
继承是面向对象编程中的一个重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类、基类或超类)继承属性和方法。
子类可以继承父类的属性和方法,并且还可以添加自己的特定属性和方法。这样可以实现代码的重用和扩展。
下面是一个简单的示例,展示了继承的基本用法:
class Animal:def __init__(self, name):self.name = namedef eat(self):print(f"{self.name} is eating.")class Cat(Animal):def meow(self):print("Meow!")class Dog(Animal):def bark(self):print("Woof!")cat = Cat("Tom")
cat.eat() # 继承自父类 Animal 的 eat 方法
cat.meow() # 子类 Cat 自己定义的方法dog = Dog("Max")
dog.eat() # 继承自父类 Animal 的 eat 方法
dog.bark() # 子类 Dog 自己定义的方法
在这个例子中,Cat
类和 Dog
类都继承了 Animal
类。它们可以使用父类 Animal
的属性和方法,同时也可以添加自己的方法。
通过继承,子类可以重用父类的代码,同时还可以通过添加新的特定方法来扩展功能。这样可以减少代码的重复编写,提高代码的可维护性和灵活性。
8.5.1 单继承
class 子类(父类):
isinstace()函数和issubclass () 函数
①isinstance()函数是Python内置的一个函数,用来判断一个对象是否属于指定的类型。
isinstance()函数的语法格式如下:
isinstance(object, type)
其中,object是要判断的对象,type是指定的类型。
isinstance()函数的返回值是一个bool类型的值,如果对象是指定类型的实例,返回True;否则,返回False。
下面是一些示例:
x = 5
print(isinstance(x, int)) # 输出True,因为x是int类型的对象y = "hello"
print(isinstance(y, str)) # 输出True,因为y是str类型的对象z = [1, 2, 3]
print(isinstance(z, list)) # 输出True,因为z是list类型的对象a = (1, 2, 3)
print(isinstance(a, tuple)) # 输出True,因为a是tuple类型的对象b = {"name": "Tom", "age": 25}
print(isinstance(b, dict)) # 输出True,因为b是dict类型的对象c = 5.5
print(isinstance(c, float)) # 输出False,因为c不是float类型的对象dog=Dog()
isinstance(dog,Dog) # 输出True,因为dog是Dog类型的对象
可以看到,isinstance()函数可以用来判断对象的类型,非常方便。
②issubclass()
函数是用来判断一个类是否是另一个类的子类的函数。
函数语法:
issubclass(subclass, classinfo)
参数说明:subclass
:要判断的类。
classinfo
:要对比的类或元组,可以是一个类对象或由类对象组成的元组
返回值:如果 subclass
是 classinfo
类的子类或者是 classinfo
类本身,则返回 True
,否则返回 False
8.5.2 多继承
多继承指的是一个类可以继承多个父类的特性和行为。在一些编程语言中,如Python,允许一个类同时继承多个父类。
多继承的优点是可以在一个类中获得多个父类的特性和行为,增加了代码的复用性和灵活性。
class 子类(父类A,父类B):
class English:def eng_know(self):print('具备英语知识')
class Math:def math_know(self):print('具备数学知识')class Student(English,Math):def study(self):print('学生的任务是学习')stu=Student()
stu.eng_know()
stu.math_know()
stu.study()执行结果:
>>> %Run main.py
具备英语知识
具备数学知识
学生的任务是学习
8.5.3 方法的重写
class Felines:def feature(self):print('猫科动物特长是爬树')
class Cat(Felines):name='猫'def feature(self):print(f'{self.name}会抓老鼠')print(f'{self.name}会爬树')cat=Cat()
cat.feature()执行结果:
>>> %Run main.py
猫会抓老鼠
猫会爬树
8.5.4 super()函数
super().方法名()
super()函数是一个用于调用父类方法的函数。它返回一个临时对象,这个对象是指向父类的一个实例,通过该对象可以调用父类的方法。在使用super()函数时,通常是在子类的方法内部使用,以便在子类中执行父类的方法,并可以对其进行扩展。通过super()函数,可以实现方法的重写、覆盖或者覆写,从而实现对继承的灵活应用。
class Felines:def feature(self):print('猫科动物特长是爬树')
class Cat(Felines):name='猫'def feature(self):print(f'{self.name}会抓老鼠')print(f'{self.name}会爬树')print('-'*20)super().feature()cat=Cat()
cat.feature()执行结果:
>>> %Run main.py
猫会抓老鼠
猫会爬树
--------------------
猫科动物特长是爬树
8.6 多态
多态的实现方式有两种:继承和接口。继承方式是通过子类继承父类的方法,然后在子类中重写父类的方法,实现多态效果。接口方式是通过定义接口,并让不同的子类实现接口中的方法,然后通过接口引用指向不同实现类的对象,实现多态效果。
多态的好处在于可以提高代码的灵活性和可扩展性。由于多态可以使得同一个方法适用于不同的对象,可以减少重复的代码,提高代码的复用性。另外,通过多态可以实现接口的抽象,可以在不改变接口的情况下扩展程序的功能。
class Animal(object): #object是所有类的父类def move(self):pass #pass用来占位保持函数结构的完整,相当于{}
class Rabbit(Animal):def move(self):print('兔子蹦蹦跳跳')class Snail(Animal):def move(self):print('蜗牛缓慢爬行')def test(obj): #通过参数的不同调用不同的move方法obj.move()rabbit=Rabbit()
test(rabbit)
snail=Snail()
test(snail)#执行结果
'''
>>> %Run main.py
兔子蹦蹦跳跳
蜗牛缓慢爬行
'''
9.异常处理
9.1 除零异常
i=input('请输入数字:') #当输入为0时,会引发程序无法执行或异常结束
n=12
result=n/int(i)
print(result)
print(f'{n}除以{i}等于{result}')
9.2 捕获异常
在Python中可使用try...except语句捕获一次,try...except还可以与else、finally组合使用实现更强大的异常处理功能。
9.2.1 try...except语句
try:
可能出错的代码
except [异常类型]:
错误处理语句
i=input('请输入数字:')
n=12
try:result=n/int(i)print(result)print(f'{n}除以{i}等于{result}')
except:print('除数不能为0!')'''执行结果
>>> %Run main.py
请输入数字:0
除数不能为0!
'''
9.2.2 捕获异常信息
①捕获程序运行时的单个异常
i=input('请输入数字:')
n=12
try:result=n/int(i)print(result)print(f'{n}除以{i}等于{result}')
except ZeroDivisionError as e: #指定异常类型为除数为零异常print('除数不能为0!')print(f'异常原因:{e}')'''执行结果
>>> %Run main.py
请输入数字:0
除数不能为0!
异常原因:division by zero
'''
②捕获程序运行时的多个异常
try:print(count)demo_list=['Python','java','C','C++']print(demo_list[4])
except (NameError,IndexError) as e:#NameError:未定义名字异常#IndexError:索引越界异常print(f'异常错误原因:{e}')
'''
执行结果
>>> %Run main.py
异常错误原因:name 'count' is not defined
'''try:#print(count)demo_list=['Python','java','C','C++']print(demo_list[4])
except NameError as e:#NameError:未定义名字异常print(f'异常错误原因:{e}')
except IndexError as e:#IndexError:索引越界异常print(f'异常错误原因:{e}')
'''
执行结果
>>> %Run main.py
异常错误原因:list index out of range
'''
③捕获程序运行时的所有异常
try:print(count)demo_list=['Python','java','C','C++']print(demo_list[4])
except Exception as e: #与"excpt :"一样捕获所有异常print(f'异常错误原因:{e}')
#excpt :
# print('程序出现异常,原因未知')
9.2.3 else子句
try:
可能出错的语句
except :
出错后的执行语句
else:
未出错时的执行语句
num=input('请输入每页显示多少条数据:')
try:page_num=int(num)
except Exception as e:page_num=10print(f'当前页面显示{page_num}条数据')
else:print(f'当前页面显示{num}条数据')'''
执行结果
>>> %Run main.py
请输入每页显示多少条数据:1
当前页面显示1条数据
>>> %Run main.py
请输入每页显示多少条数据:ts
当前页面显示10条数据
'''
9.2.4 finally子句
try:
可能出错的语句
except:
出错后的执行语句
finally:
无论是否错误都会执行的语句
9.3 抛出异常
9.3.1 raise语句
(1) 由异常类名引发异常
(2) 由异常对象引发异常
(3) 由程序中出现过的异常引发异常
#(1) 由异常类名引发异常
>>>raise NameError
#(2) 由异常对象引发异常
name_error=NameError()
raise name_error
#(3) 由程序中出现过的异常引发异常
try:num
except NameError as e:raise
9.3.2 异常的传递
异常的传递是指在程序中,当一个异常被抛出但没有在当前的代码块中被捕获处理时,它会被传递给调用栈中的上一级代码块进行处理。这样的过程会一直持续,直到异常被捕获或者到达程序的最顶层代码块。
在异常的传递过程中,如果没有捕获到异常,最终会传递到程序的最顶层代码块,如果还没有进行处理,程序将会终止运行,并输出异常信息。因此,在编写程序时,我们应该合理地设计异常处理机制,捕获并处理可能出现的异常,以保证程序的稳定性和可靠性。
def get_width():print('get_width开始执行')num=int(input('请输入除数:'))width_len=10/numprint('get_width执行结束')return width_len
def calc_area():print('calc_area开始执行')width_len=get_width()print('calc_area执行结束')return width_len*width_len
def show_area():try:print('show_area开始执行')area_val=calc_area()print(f'正方形的面积是: {area_val}')print('show_area执行结束')except ZeroDivisionError as e:print(f'捕获到异常: {e}')
if __name__=='__main__':show_area()
'''
执行结果
>>> %Run main.py
show_area开始执行
calc_area开始执行
get_width开始执行
请输入除数:1
get_width执行结束
calc_area执行结束
正方形的面积是: 100.0
show_area执行结束
>>> %Run main.py
show_area开始执行
calc_area开始执行
get_width开始执行
请输入除数:0
捕获到异常: division by zero
'''
9.4 自定义异常
class CustomError(Exception): #自定义异常类passtry:passraise CustomError('出现错误') #抛出异常
except CustomError as e:print(e)'''
执行结果
>>> %Run main.py
出现错误
'''
10. 模块
10.1 模块的概念
Python中的模块可分为三类: 内置模块、第三方模块和自定义模块
10.2 模块的导入方式
使用import导入和使用from....import....导入
①使用import导入
import 模块1,模块2,...
模块名.函数名 ()/类名
import 模块名 as 别名
'''
这段代码使用了time模块和random模块,并且还导入了sys模块。
可以使用time.sleep()函数来让程序暂停执行一段时间。
random模块可以用来生成随机数。
sys模块提供了对Python解释器的访问和控制。
'''
import time
import random,systime.sleep(1) #延时1S
②使用from...import...导入
from 模块名 mport 函数/类/变量
from time import sleep, time
from 模块名 import *
from 模块名 import 函数名 as 别名
from time import sleep as sl
sl(1) #延时1S
10.3 自定义模块
#main.py
age=13
def introduce():print(f'我的名字是小王,今年{age}岁')#test.py
import main
main.introduce()
print(main.age)'''
执行结果
>>> %Run test.py
我的名字是小王,今年13岁
13
'''#test1.py
from main import introduce
introduce()'''
执行结果
>>> %Run test.py
我的名字是小王,今年13岁
'''
10.4 模块的导入特性
10.4.1 __all__属性
__all__
属性是一个列表,用于定义模块中可以被导入的公共接口。当其他模块使用from module import *
语句时,只有__all__
列表中指定的成员会被导入。如果__all__
属性未定义或为空列表,则不允许使用from module import *
语句导入任何成员。
#calc.py文件
__all__=['add' , 'subtract'] #只允许导入add,subtractdef add(a, b):return a+b
def subtract(a,b):return a-b
def multiply(a,b):return a*b
def divide(a,b):if b:return a/belse:print('error')#test.py文件
from main import*print(add(2,3))
print(subtract(2,3))
print(multiply(2,3))
print(divide(2,3))'''
执行结果:
>>> %Run test.py
5
-1
Traceback (most recent call last):File "E:\ESP32物联网开发板\4--实验程序\1--MicroPython实验\test.py", line 5, in <module>print(multiply(2,3))
NameError: name 'multiply' is not defined
'''
10.4.2 __name__属性
__name__属性是一个内建属性,用于表示当前模块的名称。
当模块作为主程序运行时,__name__的值为'__main__'。 当模块作为被导入的模块使用时,__name__的值为模块的名称。
这个属性可用于判断在不同的模式下执行不同的代码。例如,可以在模块的主程序中加入以下代码:
if __name__ == "__main__": # 如果模块作为主程序运行,则执行以下代码 main_function()
这样,当模块作为主程序运行时,主函数main_function()会被调用;而当模块被作为被导入的模块使用时,主函数main_function()将不会被调用。
这个属性对于模块的调试和测试非常有用,可以将一些测试代码放在这个判断语句中,只有当模块作为主程序运行时才会执行这些测试代码。
all__=['add','subtract']
def add(a, b):return a+b
def subtract(a,b):return a-b
def multiply(a,b):return a*b
def divide(a,b):if b:return a/belse:print('error')
if __name__=='__main__':print(add(2,3))print(subtract(2,3))print(multiply(2,3))print(divide(2,3))
10.5 Python中的包
10.5.1 包的结构
包的结构一般由多个子包或模块组成,每个子包或模块中包含一组相关的类或功能。这种组织方式的好处是可以更好地管理代码,提高代码的可维护性和可重用性。
包的结构可以按照不同的层次或模块划分,例如按照功能划分、按照模块划分、按照层次划分等。具体的包的结构设计要根据具体的项目需求和开发规范来确定。
10.5.2 包的导入
#module_demo.py文件在package_demo包内
def add(num1,num2):print(num1+num2)#test.py文件
#方法一
from package_demo import module_demo
#package_demo包名,module_demo模块名
module_demo.add(1,3)#方法二
import package_demo.module_demo
package_demo.module_demo.add(1,3)