PYTHON编程基础回顾
面向对象基础-中
面向对象具有三大特性——继承、多态与封装;
在本部分将会讲到三大特性中的两个——继承与多态
析构方法
概述:当一个对象被删除或者被销毁时,python解释器会默认调用一个方法——
__del__()
,就称为析构方法;
特点:
①在整个程序脚本执行完毕的时候自动调用该方法;
②当对象被手动销毁时也会自动调用该方法;
③析构函数一般用于资源回收,利用该方法销毁对象可以回收内存等资源。
p.s. 通过上一节的知识,我们知道__del__()
方法也是魔术方法,是用于销毁已创建对象的。
'''
析构方法示例:
'''
#__del__()方法在程序执行结束后被自动调用
class Animal(object):
def __init__(self,name,color):
self.name = name
self.color = color
print('-----%s对象-----init方法被执行------------' % self.name)
def __del__(self):#当在某个作用域下对象没有被引用时,解释器就会自动调用该函数,释放原对象所在内存空间
print('-----%s对象----del方法被执行--------------'%self.name)
dog = Animal('小狗','白色')
cat = Animal('小猫','橘色')
print('即将手动销毁小猫这个对象......')
del(cat)
print('这是程序最后一行,程序即将结束......')
'''
运行结果:
-----小狗对象-----init方法被执行------------
-----小猫对象-----init方法被执行------------
即将手动销毁小猫这个对象......
-----小猫对象----del方法被执行--------------
这是程序最后一行,程序即将结束......
-----小狗对象----del方法被执行--------------
'''
继承
- 单继承
在面向对象中,所谓继承就是将多个类共有的方法和属性提取到父类中,子类仅需要继承父类而不必一 一实现每个方法;
p.s. 通过单继承可以让我们的代码更简洁(最大化地复用代码),逻辑更清晰,在每个子类中只需要关注其独有的实现。
'''
单继承示例:
'''
#通过将多个类中共有的方法提取到父类,子类通过继承父类减少代码的重复
class Animal(object):#定义父类
def __init__(self,name,color):
self.name = name
self.color = color
def eat(self):
print('%s在吃'%self.name)
def run(self):
print('%s在跑'%self.name)
class Dog(Animal):#定义子类,并使其继承Animal父类
def wwj(self):
print('%s汪汪叫'%self.name)
class Cat(Animal):#定义子类,并使其继承Animal父类
def mmj(self):
print('%s喵喵叫'%self.name)
d1 = Dog('旺财','黑色')
c1 = Cat('小乖','白色')
#对共有方法的调用
for item in [d1,c1]:
item.run()
item.eat()
#分别调用子类中共有的方法
d1.wwj()
c1.mmj()
'''
运行结果:
旺财在跑
旺财在吃
小乖在跑
小乖在吃
旺财汪汪叫
小乖喵喵叫
'''
- 多继承
概述:简而言之多继承就是python中的一个类可以继承多个父类的方法和属性的一种机制;
p.s. 通过用逗号来分隔继承的多个父类
继承是具有传递性的,在下面的代码例子中已经是多层继承的体现了
p.s. 在类的传递过程中,我们把父类称为基类,子类称为派生类,父类的属性和方法可以一级一级地传递给子类(一般设计中不会超过三级)
'''
多继承示例:
'''
#某个类可以通过继承多个类来获得其所继承父类的所有属性和方法
class Animal(object):
def __init__(self,name,color):
self.name = name
self.color = color
class island_live(Animal):#陆生动物类继承动物类
def lung_use(self):
print('%s在陆地用肺呼吸'%self.name)
class water_live(Animal):#水生动物类继承动物类
def gill_use(self):
print('%s在水中用鳃呼吸'%self.name)
class Amphibian(island_live,water_live):#两栖动物类同时继承水生类和陆生类
def live_place(self):
print('%s有时在水中,有时在陆地上'%self.name)
#创建实例
dolphin = Amphibian('海豚小白','蓝色')
#调用子类中独有方法
dolphin.live_place()
#调用父类中的方法
dolphin.gill_use()
dolphin.lung_use()
'''
运行结果:
海豚小白有时在水中,有时在陆地上
海豚小白在水中用鳃呼吸
海豚小白在陆地用肺呼吸
'''
- 继承中的查找顺序
如果对于层次超过一层的继承和多继承中,出现了不同类中方法重名的现象,则按照广度优先的顺序进行查找,如果最终查找不到则报错。
可以通过调用某一个类对象的魔术方法(诸如在下例中的A.__mro__
)来获得某一个类的继承顺序,也是其面对同名方法的查找顺序。
'''
继承中的查找顺序
'''
#定义一系列单继承和多继承的关系
class D(object):
def func(self):
print('调用了D的func')
class C(D):
def func(self):
print('调用了C的func')
class B(D):
pass
class A(B,C):
pass
#实例化
aa = A()
aa.func()
#运行结果:调用了B的func
print(A.__mro__)#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
父类方法
- 重写父类方法
在前文中我们探讨了如果子类自身和其继承的父类之间存在重名的方法,在调用时python解释器会按照其类的继承顺序一级一级地查找;
从另一个角度来说,在子类中如果有一个和父类相同名字的方法的话,在调用该名称的方法时,解释器会执行子类中的方法;也就是说子类中的方法会覆盖掉父类中同名的方法——这就是父类方法的重写。
- 调用父类方法
同样是在前文中讲到类的继承时说过,父类中定义的方法和属性(如果子类中没有进行重写)那么子类是可以继续用的;
从另一个角度来说,当对子类实例化后的对象使用父类中定义的方法时,实质上就是在调用父类方法。
p.s. 调用父类方法时(比如下面代码中Cat的初始化函数中调用了父类Animal
的父类)
①直接利用父类的实例方法进行调用,形如代码中的Animal.__init__()
②或者使用super
自动寻找父类并对父类的方法进行调用,super.__init__()
'''
父类方法的重写和调用
'''
class Animal(object):
def __init__(self,name,color):
self.name = name
self.color = color
def __str__(self):
return '对Animal类的str方法进行了调用'
class Dog(Animal):
def __init__(self,color,age):#对父类的初始化方法进行重写
self.color = color
self.age = age
#没有重新定义__str__()方法,在print该类的实例化对象时会调用父类的__str__()方法
class Cat(Animal):
def __init__(self,name,color,age):
Animal.__init__(self,name,color)#在重写的同时增加新的实例属性
self.age = age
def __str__(self):#对父类方法进行重写
return '小猫%s是%s的,今年%s了'%(self.name,self.color,self.age)
Dolphin = Animal('海豚','蓝白色')
dog = Dog('黑色','10个月')
cat = Cat('咪咪','灰色','5个月')
print(Dolphin)
print(dog)
print(cat)
'''
运行结果:
对Animal类的str方法进行了调用
对Animal类的str方法进行了调用
小猫咪咪是灰色的,今年5个月了
'''
属性与方法-进阶
- 类属性与实例属性
【基本概念】
类属性:类对象所拥有的属性,被所有类对象的实例对象所共有,类对象和实例对象都可以访问;
p.s.通过实例对象访问属性的时候,是经过了几级查找——首先查找对象本身的实例属性,如果不存在则再去其上一级类对象中查找,最终没找到则会报错。
实例属性:实例对象所拥有的属性,只能通过实例对象进行访问。
'''
类属性和实例属性的访问
'''
class Student(object):
vocation = '学生'#定义的类属性
def __init__(self,name,age):
self.name = name #以下二者定义的是实例属性
self.age = age
#实例化对象
xm = Student('小明',17)
#通过实例对象可以访问实例属性和类属性
print(xm.vocation) #类属性的访问,输出【学生】
print(xm.name) #实例属性的访问,输出【小明】
#通过类对象只能访问类属性
print(Student.vocation) #输出【学生】
【类属性的访问与修改】
①如果需要在类外修改类属性,必须通过类对象去引用然后进行修改;
②如果通过实例对象对类属性进行重新赋值:其本质上是产生了一个新的同名实例属性;
③在给对象添加了同名的实例属性之后,再通过实例对象引用同名属性时,实例属性会强制屏蔽调类属性;也即引用的是实例属性。
'''
类属性和实例属性的修改
'''
class Student(object):
vocation = '学生'#定义的类属性
def __init__(self,name,age):
self.name = name #以下二者定义的是实例属性
self.age = age
def __str__(self):
return '%s是个%s,今年%d岁'%(self.name,self.vocation,self.age)
#实例化对象
xm = Student('小明',17)
print(xm)
#通过实例对象引用类属性并修改
xm.vocation = '教师'
print('修改后的类属性vocaion为:',Student.vocation)
#通过上一步,给xm这个实例对象新增了实例属性
print('实际上是给xm新增实例属性......')
print(xm)
#只能通过直接引用类属性进行修改
Student.vocation = '教师'
print('直接引用类对象的属性并进行修改后:',Student.vocation)
'''
运行结果:
小明是个学生,今年17岁
修改后的类属性vocaion为: 学生
实际上是给xm新增实例属性......
小明是个教师,今年17岁
直接引用类对象的属性并进行修改后: 教师
'''
- 类方法和静态方法
- 类方法
类方法:类对象所具有的方法,需要用装饰器
@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数,类方法可以通过类对象、实例对象进行调用;
p.s.①类方法主要用于对类属性进行访问和修改;也即:类方法可以直接地、不通过实例调取类属性;
p.s.②因为类方法是对类属性进行处理,不管是通过类对象还是实例对象,最终改变的结果既会影响类本身,也会影响类实例化的对象。
'''
类方法
'''
class People(object):
country = 'China'
@classmethod # 通过装饰器声明类方法
def get_country(cls):
return cls.country #类方法可以直接调取类属性
@classmethod
def change_country(cls,nation):
cls.country = nation
#通过类对象直接调用类方法
print('通过类对象调用类方法',People.get_country())
#可以对类进行实例化,通过实例调用类方法
xw = People()
print('通过实例对象调用类方法',xw.get_country())
#通过调用类方法可对类属性进行修改
xw.change_country('Japan')
print('通过实例对象调用类方法,对类属性进行修改:(xw.country,People.country)',xw.country,People.country)
People.change_country('England')
print('通过类对象调用类方法,对类属性进行修改:(xw.country,People.country)',xw.country,People.country)
'''
运行结果:
通过类对象调用类方法 China
通过实例对象调用类方法 China
通过实例对象调用类方法,对类属性进行修改:(xw.country,People.country) Japan Japan
通过类对象调用类方法,对类属性进行修改:(xw.country,People.country) England England
'''
- 静态方法
静态方法:静态方法也是类对象拥有的方法,需要用
@staticmethod
来表示静态方法,静态方法不强制传入参数,根据方法的具体情境和需求。
p.s.①静态方法也可以既通过类对象来访问,也通过实例对象来访问,但是一般不通过实例对象来访问静态方法,因为声明为静态方法就是因为强调其与实例对象的无关性;
p.s.②静态方法主要用于存放逻辑性的代码,其本身和类以及实例对象没有交互;也就是说在静态方法中,不会涉及到类中方法和属性和相关操作;
p.s.③基于上述特点,静态方法的使用可以使得数据资源得到有效充分的利用。
'''
静态方法
'''
import time
class People(object):
country = 'China'
@staticmethod
def get_data():
return People.country #在静态方法中访问类属性需要通过类对象
@staticmethod
def showTiime():
return time.strftime("%H:%M:%S",time.localtime())
#通过类对象直接调用静态方法
print(People.get_data())
#通过实例化后的对象,也可以调用静态方法(一般不这样做)
xw = People()
print(xw.get_data())
print(People.showTiime())
- 类方法、实例方法与静态方法
方法类型 | 参数及作用 | 调用方法 |
---|---|---|
类方法 | 第一个参数对象为cls ,通过cls 引用类对象的属性和方法;需要使用装饰器@classmethod 来声明 |
可以通过类对象和实例对象进行调用 |
实例方法 | 第一个参数对象为self ,通过self 引用的可能是类对象也可能是实例对象的属性和方法,在同名情况下实例属性会覆盖类属性 |
只能通过实例对象进行调用 |
静态方法 | 不强制传入参数,如果在方法内需要引用类属性(也只能引用类属性)的话需要通过类对象来引用;需要使用装饰器@staticmethod 来声明 |
一般只通过类对象来调用 |
多态
【多态】:所谓多态,就是多种形式、多种状态,python中不同对象调用同一接口可以表现出不同状态。
p.s. ①用具体的例子讲述多态,也就是对于不同子类对象,调用同一个方法时所进行的具体操作是不一致的。
p.s. ②通过多态这种思想,可以增加程序的灵活性和可拓展性。
【多态实现的前提】:
①继承——多态必须发生在父类和子类之间;
②重写——子类重写父类的方法
p.s.这样描述很抽象,可以理解为在python中多态的实现是因为:子类与父类之间方法的继承,以及对于不同子类会根据自己的逻辑,对父类继承的方法进行重写覆盖。
【鸭子类型】
①python是弱类型语言,不支持诸如Java和C#中多态的写法,但是它是原生多态的语言,崇尚“鸭子类型”。
②鸭子类型(ducking type)是动态类型的一种风格;在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口;而是由当前方法和属性的集合决定的。在鸭子类型中,关注的不是对象的类型本身,而是它如何被使用。
'''
多态的代码示例
'''
class Animal(object):#定义基类1
def __init__(self,name,color):
self.name = name
self.color = color
def say_myself(self):
print('我是%s,是%s的动物'%(self.name,self.color))
class Dog(Animal):#定义子类
def say_myself(self):#对父类中的方法进行重写
print('我是%s,是%s的小狗'%(self.name,self.color))
class Duck(Animal):#定义子类
def say_myself(self):
print('我是%s,是%s的小鸭子'%(self.name,self.color))
class People(object):#定义基类2
def __init__(self,name,age):
self.name = name
self.age = age
def say_myself(self):
print('我是%s,今年%d岁,是个中国人')
class Pupil(People):
def say_myself(self):#对父类中的方法进行重写
print('我是%s,今年%d岁,是个中国学生'%(self.name,self.age))
objlist = [Dog('旺财','黑色'),Duck('可达鸭','黄色'),Pupil('小明',17)]
for item in objlist:
item.say_myself()
'''
运行结果:
我是旺财,是黑色的小狗
我是可达鸭,是黄色的小鸭子
我是小明,今年17岁,是个中国学生
'''
面向对象基础-下
在之前对面向对象思想中的【类】、【对象】以及【类之间的关系】进行了学习,接下来要深入学习如何具体控制属性和方法来满足逻辑需求、完成功能。
私有化
【概念】:通过声明私有化的属性和方法可以控制和保护数据
- 私有化属性
【问题提出】:对于一些重要属性不希望被随意或者意外更改
【问题解决】:将属性定义为私有属性,再添加一个可调用的方法进行访问
【语法及特点】:通过两个下划线开头(例:__age = 18
)来声明属性为私有,私有属性不可以在类的外部被使用或直接访问,且子类也不能继承父类的私有化属性。
'''
私有化属性,应用场景
①隐藏一个特定属性,不让类的外部直接调用
②保护特定属性不被随意更改
③保护特定属性不被子类修改
'''
class Person(object):
def __init__(self,name,age):
self.__name = name #使用双下划线将name属性声明为私有化的
self.age = age
def get_name(self):
return self.__name #在类的内部定义一个可调用函数
xm = Person('小明',18)
#print(xm.__name) 该调用会报错,因为私有化的属性不能在类的外部进行访问
print(xm.get_name()) #可以通过内部定义的调用接口来访问私有属性,输出【小明】
- 私有化方法
【问题提出】对于有些重要的方法,担心会被子类意外重写
【问题解决】将普通方法设置成为私有化方法,不允许外部调用,但是可以在内部进行调用
【语法及特点】在定义方法时在方法名前加两个下划线__
即可声明为私有化方法;
①一般是类内部进行调用
②子类不能对私有化方法进行继承
【python中的下划线使用】
类型 | 说明 |
---|---|
__xxx__ |
前后双下划线:表示python内置的魔术方法 |
__xxx |
前面双下划线:表示对类中的方法或属性进行私有化声明 |
_xxx |
前面单下划线:表示保护类型(protected )的变量,只允许其本身和子类进行访问,不允许from xxx import * |
xxx_ |
后面单下划线:只是一个命名技巧,避免属性名和python关键字发生冲突 |
Property属性、__new__方法和单例模式
- 属性函数(Property)
【问题背景】因为私有化属性的引入,我们为了访问和修改私有属性,一般需要写两个方法,一个进行访问一个进行修改;通过调用的方式来控制属性。
【问题提出】现在我们希望让调用者直接以访问属性的方式来访问属性,但同时我们又能对调用者的访问权限和属性安全进行控制。
- 实现方式1:在类中定义值为property对象的类属性
【语法形式】
valuename = property(get_valuename,set_valuename)
'''
Property——属性函数
引入属性函数的作用,一句话来说——能够以访问属性的方式来访问和修改私有化变量
'''
#在定义属性函数之前,对私有化属性的访问和修改
class Animal(object):
def __init__(self,color,age):
self.__kind = '动物'
self.color = color
self.age = age
def get_kind(self):#定义方法来访问私有化属性
return self.__kind
def change_kind(self,info): #定义方法来改变私有化属性
self.__kind = info
#实例化一个新对象
dog = Animal('黑色','1岁')
print('查看dog的kind属性:',dog.get_kind())#通过事先定义好的方法来访问私有化属性
dog.change_kind('犬类') #通过事先定义好的方法来修改私有化属性
print('查看dog修改过后的kind属性:',dog.get_kind())
#-------------使用属性函数之后,可以用访问属性的形式来修改和访问私有化属性
class Chinese(object):
def __init__(self,job,age):
self.__nation = '中国'
self.job = job
self.age = age
def get_nation(self):
return self.__nation
def set_nation(self,info):
self.__nation = info
nation = property(get_nation,set_nation) #重新定义了一个属性nation
#对这个属性设置值时调用set_nation,对属性值进行获取时调用get_nation
xm = Chinese('程序员',35)
print('查看xm的nation属性:',xm.nation) #可以用访问普通属性的方式访问私有化属性
xm.nation = '日本'#可以用修改实例属性的方式修改私有化属性的值
print('查看xm修改过后的nation属性:',xm.nation)
'''
运行结果:
查看dog的kind属性: 动物
查看dog修改过后的kind属性: 犬类
查看xm的nation属性: 中国
查看xm修改过后的nation属性: 日本
'''
- 实现方式2:在方法上使用装饰器
【语法形式】
①使用装饰器对valuename进行装饰,提供getter()
方法
@property
②使用装饰器提供一个setter()
方法
@valuename.settter()
class Chinese(object):
def __init__(self,job,age):
self.__nation = '中国'
self.job = job
self.age = age
@property#使用装饰器对nation进行装饰,提供了getter方法
def nation(self):
return self.__nation
@nation.setter#使用装饰器进行装饰,提供一个setter方法
def nation(self,info):
self.__nation = info
xm = Chinese('程序员',35)
print('查看xm的nation属性:',xm.nation) #可以用访问普通属性的方式访问私有化属性
xm.nation = '日本'#可以用修改实例属性的方式修改私有化属性的值
print('查看xm修改过后的nation属性:',xm.nation)
'''
运行结果:
查看xm的nation属性: 中国
查看xm修改过后的nation属性: 日本
'''
__new__
方法
【概述】
__new__
方法的作用为:创建并返回一个实例对象,如果__new__
只调用一次,就会得到一个对象;继承自object
的新式类才有这个魔术方法。
p.s.该方法虽然没有添加任何标识,但它本身是类的静态方法。
【特点】
①__new__
是在一个对象实例化的时候调用的第一个方法
②该方法至少需要有一个参数cls
(代表需要进行实例化的类),在实例化时该参数由python解释器自动提供,其他参数是用来传递给__init__
方法的;
③__new__
的具体实现逻辑决定了是否要继续调用__init__
方法,因为该方法可以调用其他类的构造方法或者直接返回别的实例对象作为本类的实例,如果__new__
没有返回实例对象则__init__
就不会被调用;
④不能在__new__
方法中再调用自己,否则会报错(超过最大递归深度),实质上也就时递归没有出口,会陷入死循环
class Test(object):
def __init__(self):
print('init方法被调用......')
self.name = '测试'
def __new__(cls, *args, **kwargs):
print('new方法被调用......')
return super().__new__(cls)#调用父类,也即object的new方法
def __del__(self):
print('del方法被调用......')
test = Test()
'''
运行结果:
new方法被调用......
init方法被调用......
del方法被调用......
'''
- 单例模式
【概念】单例模式是常用设计模式的一种,保证整个系统中只有一个实例,重复打开也只是使用着一个实例。
p.s. 也就是说,不管创建多少次对象,类返回的对象都是最初创建的,不会再新建其他对象。
- 实现步骤1:利用类属性保存初次创建的实例对象,第二次实例化的时候判断类属性是否有保存实例对象,如果有就返回类属性保存的,如果没有就调用父类
__new__
方法创建新的实例对象。
'''
单例模式代码示例
'''
#非单例模式情景下
class Nonsingle(object):
pass
obj1 = Nonsingle()
obj2 = Nonsingle()
obj3 = Nonsingle()
print('非单例模式下三个实例化对象的地址分别为:',id(obj1),id(obj2),id(obj3))
#单例模式情景下
#实现逻辑:在调用__new__方法实例化之前,先判断这个类的实例对象是否存在,若已存在则不再调用
class Single(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'):
cls._instance = super().__new__(cls,*args,**kwargs)
return cls._instance
sobj1 = Single()
sobj2 = Single()
sobj3 = Single()
print('单例模式下三个实例化对象的地址分别为:',id(sobj1),id(sobj2),id(sobj3))
'''
运行结果:
非单例模式下三个实例化对象的地址分别为: 2313555379536 2313555380320 2313555380432
单例模式下三个实例化对象的地址分别为: 2313555380600 2313555380600 2313555380600
'''
- 实现步骤2:在单例模式下每次都返回首次创建的实例对象,还要求对
__init__
方法也仅执行一次,通过类变量进行标记控制
'''
单例模式代码示例
'''
#实现逻辑1:在调用__new__方法实例化之前,先判断这个类的实例对象是否存在,若已存在则不再调用
#实现逻辑2:使用类变量判断init方法执行的次数,保证只对init方法进行一次调用,使得后面的对象与首次实例化的对象有同样属性
class Single(object):
_isinit = True #标记是否首次执行init方法
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,name,age):
if Single._isinit:
self.name = name
self.age = age
Single._isinit = False #首次init执行完后修改标记变量
def __str__(self):
return 'name:{},age:{}'.format(self.name,self.age)
sobj1 = Single('1','18')
sobj2 = Single('2','19')
sobj3 = Single('3','20')
print('单例模式下三个实例化对象的地址分别为:',id(sobj1),id(sobj2),id(sobj3))
print('三个实例化对象的属性分别如下:')
for item in [sobj1,sobj2,sobj3]:
print(item)
'''
运行结果:
单例模式下三个实例化对象的地址分别为: 1654863399400 1654863399400 1654863399400
三个实例化对象的属性分别如下:
name:1,age:18
name:1,age:18
name:1,age:18
'''
错误与异常处理
【目的】通过异常处理机制,处理异常,以提高代码的健壮性
【错误类型】
【语法格式】
try: #可能出现错误的代码块
except: #出错之后执行的代码块
else: #没有出错的代码块
finally: #不管有没有出错都执行的代码块
try
…except
语句
将可能出错的代码放在
try
里面,except
可以指定类型捕获异常。
except
里面的代码是捕获到异常时进行执行的,将错误捕获,这样程序就不会因为一段代码包异常而导致整个程序崩溃。
p.s.使用该语句捕获错误时,不是捕获越多越好,要学会在合适的逻辑层次捕获,代码简洁清晰。
'''
错误与异常处理
'''
# 1. try...except语句,对未知类型错误进行捕捉
try:
print(b)
except:
print('EX1:对未知类型错误捕捉——程序出错')
# 2. try...except语句,对已知类型错误进行捕捉
try:
print(b)
except NameError as msg:
print('EX2:对已知类型错误捕捉——',msg)
# 3. try...except语句,对多种可能类型的错误进行捕捉
# 在这种程序结构下,如果try内部的语句出错,则会自上而下查找出错的类型并输出
#找到了一个存在的错误类型后,之后的except语句不再执行
li = [0,1,2]
try:
print(li[10])
except NameError as msg:
print('EX3:对多种可能类型的错误捕捉——',msg)
except IndexError as msg:
print('EX3:对多种可能类型的错误捕捉——',msg)
# 4.当未知类型错误却想报错时显示具体错误类型
try:
print(b)
except Exception as e:
print('EX4:使用报错万能模板捕捉未知错误的类型——',e)
'''
运行结果:
EX1:对未知类型错误捕捉——程序出错
EX2:对已知类型错误捕捉—— name 'b' is not defined
EX3:对多种可能类型的错误捕捉—— list index out of range
EX4:使用报错万能模板捕捉未知错误的类型—— name 'b' is not defined
'''
try
…except
…else
语句
else
代码块中写的是,如果try
内部的语句块没有报错的话,程序会执行的内容。
'''
错误与异常处理
'''
# try ... except ... else
try:
print('我是程序')
except Exception as e:
print('如果程序出错则执行:',e)
else:
print('如果程序没出错,则会执行该条语句......')
'''
运行结果:
我是程序
如果程序没出错,则会执行该条语句......
'''
try
…except
…finally
语句
finally
代码块中写的是,不管try
内部的语句块有没有报错,程序最终都会执行的内容。
p.s. finally语句块中主要会处理:释放文件的资源、数据库的连接等
'''
错误与异常处理
'''
# try ... except ... finally
try:
print('我是程序')
print(3/0)
except Exception as e:
print('如果程序出错则执行:',e)
finally:
print('不管程序是否出错,都会执行该条语句......')
'''
运行结果:
我是程序
如果程序出错则执行: division by zero
不管程序是否出错,都会执行该条语句.....
'''
- 自定义异常
【Tips】
①自定义异常,都要直接或间接继承Error
类或Exception
类
②由开发者主动抛出自定义异常,在python中使用raise
关键字
'''
错误与异常处理
'''
# 自定义异常
class NotAge(Exception):#自定义的异常都要直接或间接继承Error或Exception类
def __str__(self):
return '[error:]你输入的数字不符合人类年龄规范'
try:
num = int(input('请输入一个数字: '))
if num <0 or num >130:
#使用raise关键字抛出异常
raise NotAge()
except NotAge as e:
print('程序执行出现错误:',e)
else:
print('程序执行未出现错误')
'''
运行结果:
请输入一个数字: 150
程序执行出现错误: [error:]你输入的数字不符合人类年龄规范
'''
属性和方法的动态添加
- 动态语言
【概述】动态语言就是在运行时可以改变其结构的语言,比如添加新的函数和对象,引进新的代码,删除已有的函数或者对代码结构进行改变。
- 动态语言:php,JavaScript,python
- 静态语言:C,C#,java
故:可以在python程序运行过程中添加属性和方法
- 运行中动态添加属性
- 实例化对象后,可以通过形如
obj.property = value
的形式给对象添加实例属性,注意该实例属性只归某一个实例独有。 - 通过形如
classname.property = value
的形式给类对象添加类属性,该属性归类对象和由该类实例化后的对象所有。
'''
动态添加属性
'''
# 动态添加实例属性
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '{}今年{}岁了'.format(self.name,self.age)
xm = Student('小明','18')
print('添加实例属性前......')
print(xm)
xm.school = '西电'
print('添加实例属性后......')
print('小明的学校是:',xm.school)
#动态添加类属性
Student.job = '学生'
print('添加类属性后,Student类多了一个job属性:',xm.job)
'''
运行结果:
添加实例属性前......
小明今年18岁了
添加实例属性后......
小明的学校是: 西电
添加类属性后,Student类多了一个job属性: 学生
'''
- 运行中动态添加方法
- 使用
types
添加实例方法
①使用诸如
types.Method(funcname,objname)
的格式给实例对象objname
绑定上已有方法funcname
。
②既然添加的是实例方法,则该方法只能归那一个实例对象独有。
'''
动态添加方法
'''
# 动态添加实例方法
import types
class Cat(object):
kind = '猫咪'
def __init__(self,name,color,age):
self.name = name
self.color = color
self.age = age
def __str__(self):
return '猫咪名字是{},{}的,今年{}了'.format(self.name,self.color,self.age)
zc = Cat('招财','白色','1岁')
print(zc)
#利用types方法绑定实例属性
def skill(self):
print('招财猫的独有技能:招财......')
zc.skill = types.MethodType(skill,zc)
print('添加实例方法后,对该方法进行调用......')
zc.skill()
'''
运行结果:
猫咪名字是招财,白色的,今年1岁了
添加实例方法后,对该方法进行调用......
招财猫的独有技能:招财......
'''
- 使用诸如
classname.func = funcname
的形式给类绑定类方法和静态方法
'''
动态添加方法
'''
# 动态添加类方法和静态方法
class Cat(object):
kind = '猫咪'
def __init__(self,name,color,age):
self.name = name
self.color = color
self.age = age
def __str__(self):
return '猫咪名字是{},{}的,今年{}了'.format(self.name,self.color,self.age)
zc = Cat('招财','白色','1岁')
print(zc)
#首先利用装饰器定义一个类方法和静态方法
@classmethod
def catch_mouse(cls):#类方法必须有一个类参数cls
print(cls.kind+'可以抓老鼠!')
@staticmethod
def sunbathe():
print('在太阳底下小憩......')
#然后通过claname.func = funcname的形式给类添加方法
Cat.catch_mouse = catch_mouse
Cat.sunbathe = sunbathe
#可以通过类对象或实例对象调用添加的类方法和静态方法
zc.catch_mouse()
Cat.sunbathe()
'''
运行结果:
猫咪名字是招财,白色的,今年1岁了
猫咪可以抓老鼠!
在太阳底下小憩......
'''
__slots__
属性
【概述】前面描述了python作为一种动态语言可以动态地添加属性;但是如果想要限制其这种能力,就可以在定义class的时候定义一个特殊的
__slots__
变量,限制这个类的实例可以添加的属性。
【规则】只有在__slots__
变量中的属性才能被添加,其他的则会添加失败——这一点可以防止其他人在调用类的时候随意添加属性和方法。
①如果使用了slots属性,则它定义了所有可能被添加的类属性(包括init方法中会用到的属性)
'''
slots属性的使用
'''
class People(object):
__slots__ = ('job','age','name')
def __init__(self,name):
self.name = name
xm = People('小明')
#通过动态添加属性的方法只能添加__slots__属性内的属性
xm.job = '程序员'
print('给xm对象添加了__slots__内的属性:',xm.name,xm.job)
#不再__slots__定义范围内的属性不能被添加
try:
xm.nation = '中国'
except Exception as e:
print('动态添加出现错误......')
print(e)
else:
print('动态添加未出现错误')
'''
运行结果:
给xm对象添加了__slots__内的属性: 小明 程序员
动态添加出现错误......
'People' object has no attribute 'nation'
'''
②如未使用slots属性定义属性范围,则每一个实例对象的所有属性存储在__dict__
字典中
③如果子类没有声明slots
属性,则其不会继承父类的__slots__
属性的设置;如果子类声明了,则其slots
属性应该是其自身声明的范围和父类范围的并集。
课后作业
- 私有化方法和单例模式
【问题需求】
- Property属性
【问题描述】
- 属性和方法的动态添加
【问题描述】
案例项目:飞机大战游戏
在采用面向对象的思想进行编程的时候,首先要分析清楚这个项目中出现了哪些对象,以及这些对象之间的关系和会采取的行为。
同时采用面向过程的思想进行项目分析时,从项目展开流程的角度切入,来分析主程序的流程。
(一)面向过程的尝试
- 创建游戏主窗体,显示背景图片
- 进行键盘事件检测
- 添加背景音乐
- 添加玩家对象并完善事件响应
①在while True的主循环之外,是对背景、玩家对象等图片的加载和初始化
②在主循环内部,显示窗体内需要出现的对象;并对事件进行捕捉和响应
import pygame
from pygame.locals import *
def main():
# 首先创建一个窗口,用于显示内容
screen = pygame.display.set_mode((350,500)) #设定窗口大小
#设定一个背景图片对象
background = pygame.image.load('background.jpg')
#设置一个标题
pygame.display.set_caption('飞机大战-游戏窗口')
while True:
# 设定需要显示的内容
screen.blit(background, (0, 0))
#获取键盘事件
eventList = pygame.event.get()
for event in eventList:
if event.type == QUIT:
exit()
elif event.type == KEYDOWN:
if event.key == K_a or event.key == K_LEFT:
print('left')
elif event.key == K_d or event.key == K_RIGHT:
print('right')
elif event.key == K_SPACE:
print('space')
# 更新显示的内容
pygame.display.update()
if __name__ == '__main__':
main()
能够在主界面显示游戏窗口,并对键盘的指定输入做出反应,在相应控制台上打印输出。
p.s. 但上述编码的过程实质上是遵循着【面向过程】的原则,当我们需要什么逻辑就在主函数中加入相应的逻辑判断,会使得代码很臃肿,出现重复逻辑。
(二)面向对象尝试
- 界面和对象的初始化
- 创建游戏对象
- 将事件响应封装成对象的行为方式
- 再定义一个主函数将游戏过程的所有逻辑贯穿起来
①因为我们对飞机的处理是要借助
pygame
这个模块一起实现的,所以在对飞机对象定义和操作时都需要传递屏幕对象screen
,以实现一些封装好的底层逻辑。
②在面向过程中我们知道显示飞机的逻辑是——先加载飞机图片,然后使用pygame
相应函数打印在屏幕上,屏幕保持动态刷新。在面向对象中,整个逻辑线保持不变,只不过将其作为飞机对象的一个方法封装起来。
③当把飞机作为一个对象封装起来了之后,就可以将对象和键盘事件作为参数传给某一函数,专门用于处理键盘事件的捕捉及回应。
①对象类
#定义飞机对象(飞机的显示与移动)
class plane(object):
def __init__(self,screen):
self.x = 250
self.y = 600
#设置要显示内容的窗口
self.screen = screen
#载入飞机的图片
self.imageName = 'my_plane2.png'
self.image = pygame.image.load(self.imageName)
def moveleft(self):
if self.x>0:
self.x -= 10
def moveright(self):
if self.x<460:
self.x += 10
def display(self):
self.screen.blit(self.image,(self.x,self.y))
②键盘事件捕捉及回应
#控制键盘检测
def key_control(planeobj):
# 获取键盘事件
eventList = pygame.event.get()
for event in eventList:
if event.type == QUIT:
exit()
elif event.type == KEYDOWN:
if event.key == K_a or event.key == K_LEFT:
print('left')
planeobj.moveleft()#直接对对象进行处理
elif event.key == K_d or event.key == K_RIGHT:
print('right')
planeobj.moveright()
elif event.key == K_SPACE:
print('space')
③主函数中的逻辑表达更加简洁
# 创建一个玩家对象
my_plane = plane(screen)
while True:
# 设定需要显示的内容
screen.blit(background, (0, 0)) #------显示游戏背景信息
my_plane.display() #-------通过对象方法调用的形式显示飞机图片
#获取键盘事件
key_control(my_plane)
# 更新显示的内容
pygame.display.update()
- 玩家发射子弹的逻辑实现
- 定义子弹类并实现其相关方法
- 给飞机对象类添加一个【发射子弹】的交互方法
①因为飞机要能够发射子弹并动态展示,则需要新建一系列子弹对象并实时更改其坐标位置
②如果子弹不断运动直到到达了窗口界面之外,则程序逻辑中需要删除这一子弹对象,因此子弹类中需要增加一个越界判断的方法
③基于上文,dislay的逻辑需要进行完善,对于每一个新建的子弹对象,先进行越界判断,然后删除越界子弹,对于保留下来的子弹进行展示并移动
④将子弹发射的逻辑和键盘空格键的事件响应绑定在一起
①子弹类的定义
#定义子弹类
class Bullet(object):
def __init__(self,screen,x,y):
self.x = x+13 #在预设的值上增加一个步长
self.y = y - 20
self.screen = screen
self.image = pygame.image.load('bullet.png')
def display(self):
self.screen.blit(self.image,(self.x,self.y))
def move(self):
self.y -= 2 #移动的步长在可视化上表现为子弹移动的速度
def judge(self):
if self.y<0 :
return True
②更新飞机对象的【display】逻辑,新增【shoot】方法
def display(self):
self.screen.blit(self.image,(self.x,self.y))
# 完善子弹的显示逻辑
needDelList = []
for item in self.bulletlist:
if item.judge():
needDelList.append(item)
#因为遍历过程不能直接修改源列表
#所以用两次循环分别处理【越界判断】和【越界删除】的逻辑
for item in needDelList:
self.bulletlist.remove(item)
for b in self.bulletlist:
b.display()
b.move()#对于保留在子弹列表里的每一个子弹,显示它并进行移动
# 发射子弹,也就是需要不断更改子弹的位置以显示动态效果
def shoot(self):
newBullet = Bullet(self.screen,self.x,self.y)
self.bulletlist.append(newBullet)
- 敌机对象的创建与随机发射的实现
- 与游戏飞机一样,创建对象并实现诸如【显示】、【移动】等方法
- 然后在游戏主逻辑函数中实例化这个对象,调用其相应的方法即可
- 通过导入随机模块来实现【随机发射子弹】的效果