什么叫做异常?异常就是指与正常情况不一致的一些情况,与预期的不一样就是异常。描述代码异常的时候通常是指代码报错,代码报错的异常问题一般都是些能被解决的小问题, 反而是那些与结果不一样又不报错的异常才是大问题
报错也是分情况类别的,我们最好是记住那些错误类型,这样能够帮助我们在以后的学习中更好的解决类似的问题
NameError
:print(a)
上述代码就会报 NameError
这样的错误
因为在此处a没有定义,所以不能被print函数调用
IndexError:
a = [1,2,3]
print(a[3])
上述代码会报 IndexError
这样的错误
因为对a进行索引的时候,索引号超出了范围,索引号在此处是0-2,而a的索引是3,索引会报错
KeyError:
a = {"name":"haha"}
print(a["names"])
上述代码会报 KeyError
这样的错误
因为关键字(key值)的名称写错了,并没有一个叫names的key值
ValueError:
a = 'a'
print(int(a))
上述代码会报 ValueError
这样的错误
因为a是表示一个字母,是一个字符,无法转变成整型
ZeroDivisionError:
1/0
上述代码会报ZeroDivisionError
这样的错误
因为0不能被放在分母的位置
当遇到具体的异常应该怎么处理:
第一步:找出出现问题的具体文件和行数
第二步:点击直接跳转到出现问题的那行
第三步:打断点进行调试
例如索引号超出范围的问题:
a =[1,2,3]
print(a[3])
先通过报错提示得到出错的行数,跳转,打断点再调试,先不执行a[3],使用调试功能中的 对表达式求值 然后一个一个去调试最后得到出现问题的原因
对表达式求值的功能在红箭头中
一般当程序遇到错误,那么程序就会终止,但是对于在现实生活中的使用来说,一个程序如果因为一个错就全盘崩掉,那么这个程序是不好的,所以要解决这个问题。如果想要当程序出现错误时应该进行提醒并且不影响后面代码的使用的话,就可以使用到try…expect
一般情况下:
因为第507行报错了,于是程序终止了
修改后:
修改之后先是使用index存储输入的整数索引号,try…except是用来表示可能会出现的错误,如果try里面的代码可以执行就不会执行expect里面的,如果不能就执行expect里面的。
为什么索引号不直接写上去要用一个变量去存呢?
因为在现实程序中这个索引号是不被控制的,可以是某个函数的结果,所以要用一个变量去接收
对于一个写代码的人来说,expect里面的代码可能会写什么呢?
第一,可能写出现异常代码执行的代码
第二,可能写异常的记录日志
第三,可能写一些告警,提示别人
显示异常信息:
上述的方法利用try…except表示可能出现的错误,但是是不会显示错误的具体信息,如果想要在知道代码错误的前提下,还想得到具体的错误消息可以使用try…except Exception as err(此处的err是可以改变成为其他名称的)
例如:
a = [1,2,3]
try:
idx = a[3]
except Exception as name:
print(f"idx发生了错误:{name}")
结果:
idx发生了错误:list index out of range
因为第三行的代码是a[3],但是索引范围是0-2,超出了索引范围,所以会报索引出错的错误(列表索引超出范围)
except后面加Exception可以表示所有的错误类型,但是还有一些专门的错误类型可以用专门的内容去捕获。Exception可以用在最后去兜底,如果错误不符合之前所有的可被捕获的错误,那就使用Exception
a = [2,3,4,5,1]
try:
idx = a[5]
except IndexError as e:
print("索引超出范围")
结果:
索引超出范围
因为此处的错误是属于IndexError,所以可以进入第5行并执行
a = [2,3,4,5,1]
try:
idx = a[5]
except KeyError as e:
print("索引超出范围")
结果:
报错
因为该错误不属于KeyError的内容,所以不能捕获到该内容
a = [2,3,4,5,1]
try:
idx = a[5]
except KeyError as e:
print("索引超出范围")
except Exception as e:
print('出错了')
结果:
出错了
因为它不会进入第5行,但是由于Exception是万能的,所以可以进入并打印
上述的内容是让软件自己判断到错误,自己抛出问题,但是还有一种方式是我们自己手工抛出错误。比如一个加法函数,只支持大于0的数字,那么输入小于零的数就会发生错误
def add(a,b):
if 0 > a or 0 > b:
raise ValueError('有错误')
return a+b
print(add(2,3))
print(add(-2,3))
上述代码是指如果a或b其中只要有一个不大于0就会触发错误,所以第5行可以执行,第六行的代码就会发生错误。值得注意的是,一旦发生了错误,不管后面写了什么都不会再执行
断言是指对一件事情的判断,如果断言成功则不会执行出东西也不会报错。如果断言失败的话就会触发AssertionError的错误,assert逗号后面引号里面的内容是如果断言失败会提示的错误消息。
expected = 5
actuall = 6
assert expected == actuall,'断言失败'
在文件的相关操作中,打开文件之后,我们有可能会忘记关闭文件,下面的操作可以自动关闭
with open('模块与包.py',encoding='UTF-8') as f:
print(f.read())
这样打开文件就不用自己再去打开,可以自动关闭文件
类是指具有共同特征或者共同行为的事物的统称
类的表示方式:
class 类名:
类的具体内容
类命名:还是与变量的命名规范差不多。一般来说类的命名是按照驼峰式命名,就是指多个单词之间,首字母大写
例如:
class MyCar:
pass
与之类似的是函数的命名,函数的命名规范也和变量的命名规范差不多,但是在存在多个单词时,一般会使用下划线:
例如:
def my_car():
print('开车')
这些虽然并不是命名的必须,但是一般会遵守这种规矩
如何得到一个类?
class MyCar:
pass
print(MyCar)
结果:
这样就可以获得一个类
对象是指类当中的的某个具体的成员 (object)
如何得到一个对象:
class Car:
pass
MyCar = Car()
print(MyCar)
结果:
这样就可以获得一个对象
对象名 = 类名+括号
在Python中是先定义类,再通过类来获得一个一个的对象。类就是相当于一个模板,而对象就是根据模板设计出来的具体物品
属性:类或者对象的特征
类属性:某种特征,只要是在该类里面的成员都具有的特征
实例属性:某种特征,成员之间的特征可能不一样
实例相当于对象
如何定义一个类属性?——可以直接在类里面定义
例如:
class Car:
wheel = True
engine = True
print(Car.wheel)
结果:True
上述代码相当于在Car类中定义了 wheel 和 engine 类,表示只要是属于Car类的对象,wheel都是True,engine都是True
获取类属性:
类名 . 属性名 就可以直接获取
修改类属性:
class Car:
wheel = True
engine = True
Car.wheel = 'hello'
print(Car.wheel)
结果:hello
还是使用 类名 . 属性名 ,然后直接将修改后的值赋予给原来的属性,那么原来对象的值就会发生改变,变成修改之后的
增加类属性:
class Car:
wheel = True
engine = True
Car.wheel = 'hello'
Car.color = 'red'
print(Car.color)
结果:re赋值d
增加类属性可以直接在定义的时候增加,但是如果想在外部增加也是可以的,依旧是要使用到 类名 . 属性名 ,然后把值赋给它。增加类属性与修改类属性在本质上是没有什么很大的区别的,修改类属性是在原本就有的类上进行修改,而增加类属性,就是通过相同的方式增加一个类的属性名并赋值
是否可以通过对象来获得类属性呢?
例如:
class Car:
wheel = True
engine = 'world'
MyCar = Car()
print(MyCar.engine)
结果: world
上述代码说明了可以通过对象来获得类属性。定义了一个Car类后,创建一个对象MyCar,之前说过类属性是指在这个类中的所有对象都会符合的特征,那么MyCar在未被修改时,自然与Car的属性一致,所以可以通过其来获得类属性
所以说,通过类可以获得类属性的值,通过属性也可以获得类属性的值
判断以下代码会执行的结果:
class Car:
wheel = True
engine = 'world'
MyCar = Car()
MyCar.engine = 'hello'
print(MyCar.engine)
结果:hello
上述代码会执行hello,因为MyCar的实例属性engine在第5行被修改成为了hello,所有不会再是world。就像一辆车在出厂时与其他的同一批是一样的,但是个人买回来之后会对其进行个性化的修改,最后就当然不会再与车厂或者说是原来的相同了。
再对下面的代码进行判断:
class Car:
wheel = True
engine = 'world'
MyCar = Car()
MyCar.engine = 'hello'
print(Car.engine)
结果:world
MyCar是一个对象,它自身的修改并不会影响类的属性。就像自己把车厂里的一辆车改装了之后,并不会对车厂里面的车有任何的影响
再判断:
class Car:
wheel = True
engine = 'world'
MyCar = Car()
MyCar.engine = 'hello'
Car.engine = 'fff'
print(MyCar.engine)
结果:hello
虽然类的属性在倒数第二行已经被改变了,但是这个改变并不会影响MyCar这个实例的属性,但是有一种情况就会影响,如果MyCar的属性在后面没有被改变的话,因为这样的话它就是会使用类的属性。就像如果车没有被领回家,那么在车厂里,都是一样的初始状态,如果设计师想改变,那么就会一起被改了,但是如果已经被领回家了的话,那么不论车厂里面的同一批车再怎么改也不会对自己的那一辆产生影响
初始化的过程:对象的产生过程
初始化函数:将对象的产生过程放入一个函数中
例如:
class Car:
def __init__(self):
print('车辆准备中')
MyCar = Car()
结果: 车辆准备中
__init__(self) #是初始化函数的固定写法,冒号里面加入初始化函数的具体内容,然后再创建一个MyCar的具体对象,当获取对象时,会自动调用初始化函数,所以会打印出 车辆准备中
__init__(self)
初始化函数有几大特征:
第一:当获取到对象的时候,会自动的被调用(上面验证过)
第二:该函数不能有返回值,如果有返回值也只能为None
之前说过函数必须要有返回值,这样才能保证在函数调用的时候有内容,但是此处是个例外
加入返回值的情况:
class Car:
def __init__(self):
print('车辆准备中')
return 'hahaha'
MyCar = Car()
结果:
错误信息也提示我们该函数不应该有返回值
我们可以不加return,但是加了return的话就应该:
class Car:
def __init__(self):
print('车辆准备中')
return None
MyCar = Car()
结果: 车辆准备中
这样就不会报错
关于self:
self是在类定义的里面来表示一个对象
例如:
class Car:
def __init__(self):
print('车辆准备中')
return None
MyCar = Car()
在上述代码中MyCar和self在本质上来说是一个东西。因为MyCar是一个具体的对象,而self只要获取到一个对象就将该对象放入self的位置,故二者在本质上都是一样的
可以通过一种方式来检测:
class Car:
def __init__(self):
print('车辆准备中')
print(f'类里面定义的{self}')
return None
MyCar = Car()
print(MyCar)
结果:
通过该结果可以看出存放的地址都是一模一样的,所以在本质上二者是一样的,是一个东西,只不过是不同时间的叫法命名不同
实例属性:将车的颜色改为黄色
class Car:
def __init__(self):
print('车辆准备中')
self.color = 'yellow'
MyCar = Car()
print(MyCar.color)
这样就可以将车的颜色改为黄色,因为MyCar.color这个属性在初始化函数的时候就已经被定义好了,可以直接使用
当然,也可以在后面的操作中改变属性的值:
class Car:
def __init__(self):
print('车辆准备中')
self.color = 'yellow'
MyCar = Car()
MyCar.color = 'red'
print(MyCar.color)
结果:red
PS:对象或者类的属性能尽量在定义的时候定义了就在定义的时候去完成
虽然说上述操作实现了在类定义里面构造一个对象,但是在后面对对象进行调用的时候还是相对比较单一,假如说在初始化函数的过程中,定义了一辆车为黄色,那么后面如果不做改变,将全部都是黄色的,还是无法实现定制化的过程,如果想实现定制化那就不能缺少初始化函数的参数
class Car:
def __init__(self,cl,lg):
print('车辆准备中')
self.color = cl
self.loge = lg
MyCar = Car('白色','奥迪')
print(MyCar.color)
print(MyCar.loge)
因为初始化函数本身就是一种函数,所以说可以传参。以上面的代码为例子,设置了两个参数,一个是车的颜色cl,一个是车的品牌lg。在函数内部把cl赋值给self.color,把lg赋值给self.loge,后面当遇到对象时,在Car里面传递两个参数就可以满足需求
当然,cl以及lg可以加默认值,如果说后面没有再去重新赋值,那么就可以使用默认值
(self这个名称是可以改的,但是一般我们都会使用self,不会改成其他的,最好是不改动)
方法就是指在类的里面去定义的函数,上面所说的–init–函数也属于方法,这些方法代表类和对象的行为特征
例如:
class Car:
def __init__(self,cl,lg):
print('车辆准备中')
self.color = cl
self.loge = lg
def drive(self):
print('正在驾驶中')
MyCar = Car('白色','奥迪')
MyCar.drive()
结果:
车辆准备中
正在驾驶中
该代码定义了两个方法,drive方法是通过对象来调用的。那么想一想是否可以用Car来调用呢?代码如下:
class Car:
def __init__(self,cl,lg):
print('车辆准备中')
self.color = cl
self.loge = lg
def drive(self):
print('正在驾驶中')
MyCar = Car('白色','奥迪')
Car.drive()
结果:
显然,答案是不可以的,因为方法里面是要有对象的,即要先得到对象,通过类Car来调用的是得不到对象的,所以不能用类来调用方法
总结:
实例方法:对象的行为,带有self,大多数的方法都是通过对象来调用
类方法:类的行为
实例方法中可以通过 self.属性 来获取实例属性,也可以通过 self.其他方法 来获取实例方法属性