面向对象编程
编程是 程序 员 用特定的语法+数据结构+算法 组成的代码来告诉计算机如何执行任务的过程 。
对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路。
面向对象编程和面向过程编程是两种编程范式。
之前的编程都是面向过程编程,面向过程编程将文件放在一个脚本中,过程像一条流水线,为了实现一个大的目标将其进行简化成一个个小的步骤来一步一步实现。
面向对象编程,通过创建类和对象创建模型,来实现对现实世界情况的模拟。
面向过程编程是在个人视角,我要先做什么,再做什么的形式。
面向对象编程则是站在上帝视角,我想让谁先做什么,再做什么的形式。
现在我们来做一个人狗大战的游戏。
请写一个小游戏,人狗大站,2个角色,人和狗,游戏开始后,生成2个人,3条狗
互相混战,人被狗咬了会掉血,狗被人打了也掉血,狗和人的攻击力,具备的功能都不一样
class Dog: # 狗的类 def __init__(self, name, live_value, attcak_num): self.name = name self.live_value = live_value # 狗的生命值 self.attack_num = attcak_num # 狗的攻击力 def bite(self, obj): # 狗咬人 obj.live_value -= self.attack_num print("%s 剩余血量 %s" % (obj.name, obj.live_value)) class Human: def __init__(self, name, live_value, attack_num): self.name = name self.live_value = live_value # 人的生命值 self.attack_num = attack_num # 人的攻击力 def beat(self, obj): # 人打狗 obj.live_value -= self.attack_num print("%s 剩余血量 %s" % (obj.name, obj.live_value)) dog1 = Dog('dog1', 100, 20) # 创建一个狗的对象 dog2 = Dog('dog1', 100, 30) dog3 = Dog('dog1', 80, 40) hum1 = Human('human1', 100, 40) # 创建一个人的对象 hum2 = Human('human2', 80, 50) dog1.bite(hum1) hum2.beat(dog2)
为什么要用面向对象?
- 使程序更加容易扩展和易更改,使开发效率变的更高。当程序的内容越来越多,如果要修改一处信息,那么代码整体需要修改的信息就会有很多,通过面向对象可以极大简化这些步骤。
- 基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
应用
面向过程:面向过程的程序设计思想一般用于那些功能一旦实现之后就很少需要改变的场景。
缺点:一套流水线或者流程就是用来解决一个问题,可扩展性差。
面向对象:当然是应用于需求经常变化的软件中,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
优点:面向对象解决了面向过程可扩展性差的问题,面向对象更加注重对现实世界而非流程的模拟,是一种“上帝式”的思维方式。
缺点:编程的复杂度远高于面向过程,不了解面向对象而立即上手并基于它设计程序,极容易出现过度设计的问题,而且在一些扩展性要求低的场景使用面向对象会徒增编程难度。
类与对象
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体。
那么在程序中是先有类还是先有对象?
在现实中,应该是先有一个个对象,再归结成类;
在程序中,一定要先有类,再实例化生成一个个对象。
class Luffycity_student: school = 'luffycity' def __init__(self, name,age): self.name = name self.age = age def learn(self): print("%s is learning" % self.name) def eat(self): print("%s is eating" % self.name)
注意:
- 类中可以有任意python代码,这些代码在类定义阶段便会执行,因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过__dict__()方法查看。
- 类中定义的名字,都是类的属性,点是访问属性的语法。
- __init__是对象初始化的方法。
类的使用
引用类的属性
Luffycity_student.school #查 Luffycity_student.school='Oldboy' #改 Luffycity_student.x=1 #增 del Luffycity_student.x #删
当属性修改时
class Luffycity: school = 'luffycity' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # self = stu1本身 def eat(self): print("is eating") def sleep(self): print("is sleeping") stu1 = Luffycity('alex', 23, 'male') # 相当于传入了(stu1,'alex', 23, 'male')四个参数 stu2 = Luffycity('egon', 24, 'male') # 创建对象时会激发init方法的执行 print(stu1.__dict__) # {'name': 'alex', 'age': 23, 'sex': 'male'} print(stu1.name) # alex 查属性 print(stu1.age) # 23 stu1.name = 'yuanhao' # 修改属性 print(stu1.name) # yuanhao print(stu1.__dict__) # {'name': 'yuanhao', 'age': 23, 'sex': 'male'} 对应的名称空间也发生改变 del stu1.age # 删除属性 print(stu1.__dict__) # {'name': 'yuanhao', 'sex': 'male'} stu1.hobby = 'girls' # 增加属性 print(stu1.__dict__)
调用类,或称为实例化,得到程序中的对象
s1=Luffycity_student() s2=Luffycity_student() s3=Luffycity_student() #如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__
__init__方法
#注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值
class Luffycity_student: school = 'luffycity' def __init__(self,name,age): self.name = name self.age = age def learn(self): print("%s is learning" % self.name) def eat(self): print("%s is eating" % self.name) s1 = Luffycity_student('alex', 23) s2 = Luffycity_student('egon', 25) s3 = Luffycity_student('wupeiqi',24) # 每实例化一个对象就会自动调用__init__方法 # 这样每个对象就有不同的age 和 name
对象的使用
print(s1.name) # 查 s1.sex = 'male' # 增加属性 s1.name = 'mjj' # 改 del s1.name # 删
补充:现实中的类与程序当中的类不同,有时候根据不同的需要来定义不同的类,甚至是现实中不存在的类。
属性查找
类有两种属性:数据属性和函数属性。
1.数据属性是共享的,类的所有对象都可以调用的,无论是类调用还是对象调用都指向同一块内存空间。
2.函数属性是绑定给对象用的,成为绑定到对象的方法。
类中的方法 对其本身而言就是一个普通的函数,但对于对象而言是绑定方法 。
对象的函数属性,执行这个函数实现的功能是相同的,但是对于不同的对象执行方法不同,该方法对应的内存空间也不相同。
就比如两个人虽然都是吃,但是每个人有每个人的吃法,虽然吃的功能是相同的。
对象自身独有的属性是在对象自己的名称空间当中
所有对象的共有属性放在类的名称空间当中
class Luffycity: school = 'luffycity' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # self = stu1本身 def eat(self): print("%s is eating" % self.name) def sleep(self): print("%s is sleeping" % self.name) stu1 = Luffycity('alex', 23, 'male') stu2 = Luffycity('egon', 24, 'male') print(Luffycity.school, id(Luffycity.school)) # luffycity 1267522222000 print(stu1.school, id(stu1.school)) # luffycity 1267522222000 print(stu2.school, id(stu2.school)) # luffycity 1267522222000 print(Luffycity.eat) # <function Luffycity.eat at 0x000001ED792D12F0> print(stu1.eat) # <bound method Luffycity.eat of <__main__.Luffycity object at 0x000001ED6A2C03C8>> print(stu2.eat) # <bound method Luffycity.eat of <__main__.Luffycity object at 0x000001ED6A2C0400>> Luffycity.eat(stu1) # 类本身在调用函数属性时需要传入一个对象 stu2.eat() # 对象调用函数属性时 就会自动将自己作为第一个对象传入 Luffycity.x = "from Lufycity" stu1.x = " from stu1" print(stu1.x) # from stu1 print(stu2.x) # from Lufycity
类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将谁本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)。
注意:绑定到对象的方法有自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
属性的查找顺序:
对象在调用属性时,如果自身有这个属性,类也有这个属性,会优先使用自己的,就像函数一样从内向外找。
当在自己的属性当中没找到时,才会去类当中找,自己的类中没有就会到父类当中找,以此类推。如果没有找到就直接报错,不会找全局变量。
类即类型
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类。
# Python 中一切皆对象,Python3统一了类与类型的概念 class Luffycity: school = 'luffycity' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # self = stu1本身 def eat(self): print("%s is eating" % self.name) def sleep(self): print("%s is sleeping" % self.name) stu1 = Luffycity('alex', 23, 'male') stu2 = Luffycity('egon', 24, 'male') list_1 = [1, 2, 3] # 相当于list_1 = list([1,2,3]) 就是实例化了一个对象 python中所有的数据都是实例化的对象 # 所以说python中一切皆对象 print(type(list_1)) # <class 'list'> python中的数据类型其实也是类 print(Luffycity) # <class '__main__.Luffycity'> l1 = [1, 2, 3] l2 = [] l1.append(4) # append是对象l1的绑定属性 l2.append(3) # l1 l2 都有append方法 但是l1 append 和 l2 append 执行效果是不同的 所以说不同对象的绑定方法执行也不相同 list.append(l1, 5) # 这两种方法实际上是相同的 print(l1)
小结
1.面向对象编程可以将数据与专门操作该数据的功能整合到一起,对同一个对象输入不同的数据,进而使其实现不同的功能。
2.可扩展性高,如果想给多个对象增加同一个属性,就可以通过类简单实现。
继承与派生
继承是类与类之间的guanxi,是一种什么是什么的关系,继承的功能之一就是减少代码重用问题。
继承是创建类的一种方式。在python中,一个新建的类可以继承一个类或者多个父类,父类又可以称为基类或超类,新建的类为派生类或子类。
class Hero: def __init__(self,live_value,attack_value): self.live_value = live_value self.attack_value = attack_value def attack(self, attack_obj): attack_obj.live_value -= self.attack_value class superman: pass class Riven(Hero): # 继承了Hero类 子类会继承父类所有的数据属性和函数属性 pass class Rival(Hero,superman): # python支持多继承,用逗号分隔开多个继承的类。 pass print(Rival.__bases__) # 可通过__base__方法可以查看继承的父类
print(Riven.__bases__)
(<class '__main__.Hero'>, <class '__main__.superman'>)
(<class '__main__.Hero'>,)
经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
4.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类。
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
print(Hero.__base__) print(superman.__base__) #<class 'object'> #<class 'object'>
继承与抽象
抽象即抽取类似或者说比较像的部分。
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
class Hero: def __init__(self,live_value,attack_value): self.live_value = live_value self.attack_value = attack_value def attack(self, attack_obj): attack_obj.live_value -= self.attack_value class Riven(Hero): # 继承了Hero类 子类会继承父类所有的数据属性和函数属性 pass class Rival(Hero): pass riven = Riven(100, 30) rival = Rival(100, 60) riven.attack(rival) rival.attack(riven) print(rival.live_value) print(riven.live_value)
通过继承的方式,继承父类,那么子类就会拥有父类的全部属性,本身即使什么也不写也依然可以用父类的方法。
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一大部分设置,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。
属性查找
# 属性查找顺序 class Foo(): def f1(self): print(' form Foo.f1') def f2(self): print('from Foo.f2') self.f1() # self 是对象本身 执行时仍然按照顺序 现在对象本身找 再到类中找 执行的是Bar的f1() class Bar(Foo): # 继承父类 def f1(self): print('from Bar.f1') li = Bar() li.f2() # 查找属性时,先从对象本身开始找,即__init__里面找 没有就会从类当中找 类中没有就从父类找 一层层往上找 没有找到就报错 # 执行f2 对象本身没有 到类中查找 类中没有 到父类查找 执行父类的f2 # 执行结果 from Foo.f2 # from Bar.f1
属性查找的顺序:对象本身--->自己的类--->父类--->未找到报错。
注:self是对象本身。
派生
子类除了继承父类的属性外,也可以自己派生出新的属性。如果属性与父类相同,调用时则以自己定义的为准,并不影响父类的属性。
class Hero: def __init__(self,live_value,attack_value): self.live_value = live_value self.attack_value = attack_value def attack(self, attack_obj): attack_obj.live_value -= self.attack_value class Riven(Hero): camp = 'red' # 子类有可能派生出新的属性 当子类的属性和父类的属性重复时 优先调用自己的属性 def attack(self, attack_obj): print("from Riven class") class Rival(Hero): camp = 'blue' pass
有时,也可能会运用到父类的重名属性,这时只需像调用一个普通函数一样调用父类方法即可。
class Riven(Hero): camp = 'red' # 子类有可能派生出新的属性 当子类的属性和父类的属性重复时 优先调用自己的属性 def attack(self, attack_obj): Hero.attack(self,attack_obj) # 类调用对象的方法时,不会自动传参,因此要把self传入 print("from Riven class")
继承的实现原理及继承顺序
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类(即左右两个类,先选左边)
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先。
python2中有新式类、老式类, 新式类继承object, 老式类不继承。
python3中默认是新式类。
多继承时,老式类按照深度优先,新式类按照广度优先查找。
深度优先: 一路走到底,再从第二条路开始。
广度优先: 一道路不走到底, 快要走到底时 ,返回走第二条路 最后走到底。
class A: def test(self): print("from A") super().__init__() class B(A): def test(self): print("from B") class C(A): def test(self): print("from C") class D(B): def test(self): print("from D") class E(C): def test(self): print("from E") class F(D,E): def test(self): print("from F") obj = F() obj.test() # F->D->B->E->C->A #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C print(F.mro()) # 打印属性的查找顺序 只有新式类才有这种方法
在子类中调用父类的方法
在子类派生出新的方法时,往往需要重用父类的方法,有两种方法。
方法一: 父类.方法() 调用时看作普通函数,要传入self参数。
class Hero: def __init__(self,live_value,attack_value): self.live_value = live_value self.attack_value = attack_value def attack(self, attack_obj): attack_obj.live_value -= self.attack_value class Riven(Hero): camp = 'red' def attack(self, attack_obj): print("from Riven class") Hero.attack(self, attack_obj) # 指名道姓调用父类的方法,普通的函数调用,不依赖继承 class Rival(Hero): camp = 'blue' pass
第二种方式:super()
# 第二种方式super() class Hero: camp = 'hero' def __init__(self, live_value, attack_value): self.live_value = live_value self.attack_value = attack_value def attack(self, attack_obj): attack_obj.live_value -= self.attack_value class Riven(Hero): camp = 'red' def attack(self, attack_obj): # super(Riven, self).attack(attack_obj) # super(Riven,self)返回了一个特殊的对象 只要是对象调用的就是绑定方法 因此自动会传入自身 这样就相当于调用了父类方法 # python3中可以进行简写 super().attack(attack_obj) # 效果相同 依赖于继承 print("form Riven class") print(super().camp) # 也可以调用父类的数据属性 class Rival(Hero): camp = 'blue'
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找。
#A没有继承B,但是A内super会基于C.mro()继续往后找 class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() #打印结果:from B print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
组合与重用性
减少代码重用的另一种方式是组合。
组合是指一个类的属性是另一个类的对象作为数据属性,成为组合。
组合是类与类之间什么有什么的关系。
class People: school = 'luffycity' # 通过继承 减少一定代码 def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Teacher(People): def __init__(self, name, age, sex, level, salary): # self.course_name = couese_name # self.course_price = course_price # self.couse_time = course_time super().__init__(name, age, sex) self.level = level self.salary = salary def teach(self): print("%s is teaching" % self.name) class Student(People): def __init__(self, name, age, sex, time): # self.course_name = couese_name # self.course_price = course_price # self.course_time = course_time super().__init__(name, age, sex) self.time = time def learn(self): print("%s is learning" % self.name) class Course(): def __init__(self, course_name, course_price, course_time): self.course_name = course_name self.course_price = course_price self.course_time = course_time def tell_info(self): print('课程<%s> 价格<%s> 周期<%s>' % (self.course_name, self.course_price, self.course_time)) python = Course('python', 3000, '3mons') linux = Course('linux', 4000, '4mons') teacher_1 = Teacher('alex', 18, 'male', 10, 3000) teacher_2 = Teacher('egon', 28, 'male', 10, 2300) teacher_1.course = python # 令teacher的属性 是python这个对象 teacher_2.course = linux print(teacher_1.course) # <__main__.Course object at 0x0000023346500780> teacher_1.course.tell_info() # 课程<python> 价格<3000> 周期<3mons>
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
抽象类
抽象类是一个特殊的类,抽象类只能被继承,不能被实例化,并且子类必须有抽象类中的方法。
有一些类有相同的功能,但是实现功能的方式不一样,如果每一个类都单独调用,实现起来非常复杂。这时就需要一个统一的标准,可以调用这个功能,同时保持不同类的实现方式。
通过抽象类,就可以实现不同的类有共同的使用方法,只要你知道他的功能,那么不管对象属于哪个类都可以使用同样的方法,类似于接口的功能。
抽象类实现的功能就是归一化,极大降低了使用者的使用复杂度。
# class Animal: # def run(self): # pass # # def eat(self): # 通过这样也可以实现标准化的功能 但是写的类仍然可以不去用 run和 eat 因此需要一个强制的标准 # pass import abc class Animal(metaclass=abc.ABCMeta): # 通过这种方法就可以强制被继承的类一定要有下面的两种方法 否则会报错 这种类就叫做抽象类 # 抽象类只能被继承 不能实例化对象 # 抽象类实现的功能就是归一化标准 减少使用者的使用复杂度 # 抽象类本质上还是一个类 可以有数据属性 进行属性查找时 仍然按照之前的流程
@abc.abstractmethod def run(self): pass @abc.abstractmethod def eat(self): pass class People(Animal): def walk(self): print("people is walking") def eat(self): print("people is eating") class Pig(Animal): def run(self): print("pig is runing") def eat(self): print("pig is runing") class Dog(Animal): def zou(self): print("people is zouing") def eat(self): print("people is eating") peo1 = People() pig1 = Pig() dog1 = Dog() # people pig dog都有相似的走的功能 但是调用的方法却不一样 # 因此为了降低使用者的复杂度 应该有一套标准 使相似的功能调用的方式相同
抽象类的本质还是类,指的是一组类的相似性,包括数据属性和函数属性,而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
多态与多态性
多态是指一种事物有多种形态。
比如动物有猪、狗、人。
import abc class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod def walk(self): pass @abc.abstractmethod def eat(self): pass class People(Animal): def walk(self): pass def eat(self): pass class Pig(Animal): def walk(self): pass def eat(self): pass class Dog(Animal): def walk(self): pass def eat(self): pass
多态性
多态性是只在不用考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性。
静态多态性:任何数据类型都可以通过运算符+进行运算。
动态多态性:代码如下
peo1 = People() pig1 = Pig() dog1 = Dog() # 这时候不用去考虑peo1 pig1 是什么具体类型 他们只要继承了动物类就一定有walk eat的方法 因此不需要考虑是什么类型 直接调用即可 peo1.walk() pig1.eat()
更进一步,我们也可以规定一个统一的接口来实现。
def func(animal): animal.walk() func(peo1) func(pig1) func(dog1)
多态性的优点
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)。
2.增加了程序的可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,只要是继承了animal类,就可以用func(animal)去调用 。
鸭子类型
Python崇尚鸭子类型,如果看起来像,叫声像,走路也像,那么你就是一只鸭子。
class File: def write(self): pass def read(self): pass class Textfile(File): def write(self): pass def read(self): pass class Disk: # 如果有一个对象没有继承File类 但是和File类很相似 # 那么就可以认为Disk就是file 通过file类的用法来用Disk类实例化的对象 即鸭子类型 def write(self): pass def read(self): pass disk = Disk() # disk 和 file 一样都有read write方法 disk.read() disk.write()
序列类型有多种形态:字符串,列表,元组,但他们没有直接的继承关系
但他们三个是序列化的数据类型,只要是这种数据类型,就有__len__方法。
#str,list,tuple都是序列类型 s=str('hello') l=list([1,2,3]) t=tuple((4,5,6)) #我们可以在不考虑三者类型的前提下使用s,l,t s.__len__() l.__len__() t.__len__() def len(obj): # 定义一个统一的接口来调用 return obj.__len__() print(len(l1)) print(len(t1)) print(len(s1)) #实际上len 是内置的方法了
比如列表有切片、索引查找 ,那么字符串也可以作切片索引查找,通过这种鸭子类型,大大降低了使用的复杂度。
封装
在Python中,可以用双下划线开头的方式将属性隐藏起来,即设置成为私有的属性。
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
- 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
- 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的
这种变形需要注意的问题:
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。
2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形。
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。
封装的意义
1:封装数据
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher: def __init__(self,name,age): self.__name=name self.__age=age def tell_info(self): print('姓名:%s,年龄:%s' %(self.__name,self.__age)) # 规定被访问数据的格式 def set_info(self,name,age): # 规定了修改信息的格式 必须遵循这种格式 信息才能被修改 if not isinstance(name,str): raise TypeError('姓名必须是字符串类型') if not isinstance(age,int): raise TypeError('年龄必须是整型') self.__name=name self.__age=age t=Teacher('egon',18) t.tell_info() # 提供从外部访问内部属性的接口 t.set_info('egon',19) t.tell_info()
2:封装方法:目的是隔离复杂度
比如在取款时,用户在操作时并不需要知道操作的各个流程,只需要功能的实现 ,因此可以将其他的功能隐藏起来 ,提高了安全性。
class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money()
通过这种方式,用户在取款时,内部实现了很多功能,但总体呈现出来的只有取款。
特性(property)
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。
class People: def __init__(self, name, age, height, weight, hobby): self.name = name self.age = age self.height = height self.weight = weight self.__hobby = hobby @property def bmi(self): return self.weight/(self.height**2) @property def hobby(self): return self.__hobby peo1 = People('alex', 18, 1.85, 80, 'girls') # bmi看起来像是一个数据属性 但是却必须通过计算当作一个函数属性 # 但是如果使用者非要将其当作一个数据属性来调用 就要用property print(peo1.bmi) # hobby是一个私有的属性外部无法直接访问 只能通过接口来访问 # 但是hobby看起来像一个数据属性 如果想要把他当作数据属性来用 也用property print(peo1.hobby)
当一个对象的属性看起来是一个数据属性,但却需要通过函数来实现时,如果想要用数据属性的方式来调用就可以用property,这种特性的使用方式遵循了统一访问的原则。
@property def hobby(self): return self.__hobby @hobby.setter def hobby(self, val): # 指定修改的要求 if not isinstance(val, str): print(" hobby must be str") return self.__hobby =val @hobby.deleter def hobby(self): print("不允许删除操作") # 如果想要修改hobby # peo1.hobby =1333 AttributeError: can't set attribute # 会报错 # 但是如果一定想修改的话 也可以 # 通过定义同名的函数加上另外一个装饰器就可以实现目的 peo1.hobby = 'beauty' print(peo1.hobby) # 修改成功 beauty del peo1.hobby # 不允许删除操作
property 是对使用者进行了隐藏使其认为调用的只是数据属性,但其实是一个函数属性,也体现了封装的思想。
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;
而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。
这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
class House: def __init__(self, price, owner, length, width, height): self.price = price self.owner = owner self.__length = length self.__width = width self.__height = height def tell_area(self): # 对外的接口 隐藏了实现细节 print(self.__length*self.__width) house = House(10000, 'alex', 10, 10, 10) house.tell_area() # 通过此方法可以计算面积 # 如果我想计算体积 只需要修改tell_area 而不需要修改调用方法 进而就增加了的程序的可扩展性
绑定方法与非绑定方法
类中定义的函数分为两大类:绑定方法和非绑定方法。
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入)
1.绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入(其实对象也可调用,但仍将类当作第一个参数传入)
2.绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入(属于类的函数,可以通过类调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通函数而已。
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值。
而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说。
# 绑定方法: # 绑定的对象调用方法时会把自己作为第一个参数传入 # 绑定到对象:不加任何修饰的方法 # 绑定到类: 经过@classmethod修饰的方法 # 非绑定方法: # 不与对象和类绑定的方法 即普通函数 要用@staticmethod方法修饰 class Foo: def __init__(self,name,age): self.name = name self.age = age def func(self): pass @classmethod def func2(cls, name): print('%s------%s' %(cls, name)) @staticmethod def func3(self): pass f = Foo('alex', 23) print(f.func) # <bound method Foo.func of <__main__.Foo object at 0x0000027A6B1AD550>> # 将绑定的对象作为第一个参数传入 print(Foo.func) # <function Foo.func at 0x0000026AE5A612F0> # 类可以调用但要按照普通函数的规则来 没有自动传值的功能 print(Foo.func2) # <bound method Foo.func2 of <class '__main__.Foo'>> print(f.func2('alex')) # 其实对象也可调用绑定到类的方法,但仍将类当作第一个参数传入 print(f.func3) # <function Foo.func3 at 0x0000027A7A1C1400> print(Foo.func3) # <function Foo.func3 at 0x000001FBC5601400>
内置方法
一. isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo(object):
pass
obj = Foo()
isinstance(obj, Foo) # True
issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
pass
class Bar(Foo):
pass
issubclass(Bar, Foo) # True
反射
python面向对象中的反射:通过字符串的形式操作对象相关的属性。
python中一切皆对象,一切数据都可以使用反射。
# 反射就是通过字符串来操作对象的属性 class People: country = 'China' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def talk(self): print("%s is talking" % self.name) peo1 = People('alex', 28, 'male') print(peo1.age) # 属性查找 本质上是去找 __dict__['name']
print(hasattr(peo1, 'age')) # hasattr()方法 判断对象是否有这个属性或方法
print(getattr(peo1, 'name', None)) # getattr() 方法 得到对象的属性值 没有就返回默认值 print(getattr(People, 'country')) # 类也是对象 类也可以用这些方法 setattr(peo1, 'sex', 'female') # setattr() 方法 修改对象的属性值 print(getattr(peo1, 'sex'))
delattr(peo1, 'sex') # delattr() 删除对象的属性 print(peo1.__dict__)
应用举例
class User: def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) def run(self): print('runinging') def input(self): while True: ind = input(">>>>: ").strip() if hasattr(self, ind): cmd = getattr(self, ind) cmd() user1 = User('alex', 23) user1.input()
反射的好处
1.实现可插拔机制:反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用。
可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr -----------------------------------------程序未完成--------------------------------------- #from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): # 只有当上面代码的功能完成后 才会执行 func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')
2. 动态导入模块
__setitem__,__getitem__,__delitem__,__str__
# item系列 通过这三种方法可以实现像字典一样对对象的属性进行修改 class Foo: def __init__(self, name, sex): self.name = name self.sex = sex def __getitem__(self, item): # 在对象通过f['name']来查找属性时调用 print('getitem') target = self.__dict__[item] # 可通过此种方式来查值 return target def __setitem__(self, key, value): # 在作f['age'] = 18 设置操作时出发 self.__dict__[key] = value def __delitem__(self, key): # 在作del f['name'] 删除操作时触发 self.__dict__.pop(key) def __str__(self): # 在打印时触发这个操作 并且要返回字符串 return 'name:<%s> sex:<%s>' % (self.name, self.sex) f = Foo('alex', 'male') # print(f['name']) f['age'] = 18 print(f.__dict__) # del f['name'] # print(f.__dict__) # __str__ print(f) # <__main__.Foo object at 0x00000208EA08DFD0> # 打印f后得到一个对象 但是我想要更有效的信息 比如把f的内容打印出来 这时候 就会用到__str__方法
__del__
#__del__ class Open: def __init__(self, filename): self.filename = filename print('hahah') def __del__(self): print("回收资源") def __new__(cls, *args, **kwargs): # 在__init__前调用 作一些初始化前的工作 print(cls) return object.__new__(cls) # 调用父类的new 从而执行__init__ f = Open('test.py') # 这个操作涉及两部分的资源 一部分是应用程序层面上进行了赋值操作 另一部分是程序给操作系统发送了一个请求 # 让操作系统打开这个文件 # f.read() # 打开文件之后也是 向操作系统发出请求 # f.write() # 所以整个过程调用了两部分的资源 应用程序和操作系统 程序执行完后 应用程序的内存资源被释放 # 如果没有关闭文件 那个操作系统对文件资源仍是占用的状态 没有被释放 因此最后要写f.colse()使资源被释放 # __del__方法就是在 f 被删除前会调用这个方法 可以用来回收系统的资源 # # print('>>>>>>')
__init__,__new__,__call__
__init__用于初始化一个新实例,控制初始化的过程,如添加一些属性或作一些额外的操作,在实例创建之后发生。属于实例级别的方法。
__new__接收的参数与__init__相同,用于创建一个新实例,__init__中的self实际上就是__new__创建的实例,发生在__init__前。是类级别的方法。
__call__构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()。
元类
知识储备:
1.exec的使用
exec:三个参数
参数一:字符串形式的命令
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
class_body = """ country = 'Chinese' def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) """
class_dict = {} # 可以将exec内的代码当作一个函数执行 会将执行期间的名字放在局部名称空间中 即执行exec可以得到局部名称空间 exec(class_body, globals(), class_dict) # 使用exec 三个参数 执行的字符串 全局作用域 以及局部作用域; 全局作用域未指定 默认globals(); 局部作用域未指定 默认locals() print(class_dict) # {'country': 'Chinese', '__init__': <function __init__ at 0x0000023DDD0D1268>,'talk': <function talk at 0x0000023DDD0D12F0>}
2. 产生的新对象 = object.__new__(继承object类的子类)
对象
满足以下三个条件就可以叫做一个对象:
可以赋值给一个变量
可以作为函数参数进行传递
可以作为函数的返回值
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),
因而我们可以将类当作一个对象去使用。即类也是对象。
class Foo: pass f1=Foo() #f1是通过Foo类实例化的对象 # type函数可以查看类型,也可以用来查看对象的类,二者是一样的 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建 print(type(Foo)) # 输出:<type 'type'>
type是所有新式类的类,所有类都可以说是type创建的
object是所有新式类的父类
print(object.__class__) print(object.__bases__) print(object.__class__) print(type.__bases__) ''' <class 'type'> # object类是一个type元类所创建 () # 说明 object类已经处于继承链条的顶端,是所有类的父类。 <class 'type'> # type自身的类就是type,就是说type元类也就是由type自身创建的。 (<class 'object'>,) # type这一元类的父类是object。
元类
元类是类的类,是类的模板。
元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为。
元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)。
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。
创建类的两种方式:
1.使用class关键字
class Chinese(object): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name)
2.用type创建类
# class Chinese ------> Chinese = type(....) # 第二种方法 将创建类的步骤拆分 模拟去创建 class_body = """ country = 'Chinese' def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) """ class_dict = {} # 可以将exec内的代码当作一个函数执行 会将执行期间的名字放在局部名称空间中 即执行exec可以得到局部名称空间 class_bases = (object,) # 基类通过元组来表示 若有继承 应添加进该元组 exec(class_body, globals(), class_dict) # 使用exec 三个参数 执行的字符串 全局作用域 以及局部作用域 print(class_dict) # {'country': 'Chinese', '__init__': <function __init__ at 0x0000023DDD0D1268>, 'talk': <function talk at 0x0000023DDD0D12F0>} # 定义类的三要素 类名 类的基类 类的名称空间 Chinese = type('Chinese', class_bases, class_dict) # type为创建类的类 成为元类 print(type(Chinese)) # <class 'type'> print(Chinese.country) # Chinese f = Chinese('alex', 18) f.talk() # alex is talking
自定义元类控制类的行为
一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类。
__metaclass__属性:首先我们写下class Foo(object),但是类对象Foo还没有在内存中创建。
自定义元类控制类的创建
class Mymeta(type): # 自定义元类只是重写type的一部分方法 别的方法仍要继承 def __init__(cls, class_name, class_bases, class_dict): # 在type创建类时要传入三个参数 因此自定义元类也要传入三个参数 if not class_name.istitle(): # 检查类名的首字母是否大写 不是则报错 raise TypeError("首字母必须大写") if '__doc__' not in class_dict or not class_dict['__doc__'].strip(): # 检测类有没有写注释或者或者注释是否为空 raise TypeError("必须写注释") super(Mymeta, cls).__init__(class_name, class_bases, class_dict) # 重写一部分功能 剩下的功能要继承type 的 __init__方法 # 通过这种方式 实现了自定义的元类对类的创建进行了控制 class Chinese(metaclass=Mymeta): """sdsd""" country = 'Chinese' def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) obj = Chinese('alex', 18) print(Chinese.__dict__) # __doc__就是类的注释
自定义元类控制类的实例化
知识储备:__call__
class People(object,metaclass=type): def __init__(self,name,age): self.name=name self.age=age def __call__(self, *args, **kwargs): print(self,args,kwargs) # 调用类People,并不会出发__call__ obj=People('egon',18) # 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3) obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}
总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj。
对象在被调用时 会触发__call__方法,类也是对象,相当于type.__call__(People,'alex',23)
因此可以推断 type内部有__call__方法,所以自定义类控制实例化时,也要规定__call__方法。
在创建对象时干了三件事情:1.创建一个空对象obj 2.对obj进行初始化 3.返回obj
class Mymeta(type): #继承默认元类的一堆属性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('类名首字母必须大写') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、实例化People,产生空对象obj obj=object.__new__(self) #2、调用People下的函数__init__,初始化obj self.__init__(obj,*args,**kwargs) #3、返回初始化好了的obj return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) obj=People('egon',18) # 触发Mymeta 的 __call__方法 print(obj.__dict__) #{'name': 'egon', 'age': 18}
也可以这样写
class Mymeta(type): #继承默认元类的一堆属性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('类名首字母必须大写') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj obj=self.__new__(self,*args,**kwargs) #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值 return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) def __new__(cls, *args, **kwargs): obj=object.__new__(cls) cls.__init__(obj,*args,**kwargs) return obj obj=People('egon',18) print(obj.__dict__) #{'name': 'egon', 'age': 18}
执行的顺序,People()限制性Mymeta的__call__,再执行People的__new__,最后执行People的__init__。
这段代码与上一段的区别是把创建对象初始化的阶段放在了People类中。
通过元类实现单例模式
如果创建的多个对象各个属性都是相同的,就使其只创建一个对象。即相同内容的对象内存地址相同。
比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
class Mysql: __flag = None # obj def __init__(self): self.host = '127.106.0.1' self.port = 8000 def conn(self): pass @classmethod def singleton(cls): obj = cls() # 创建一个对象 是类的方法 if not cls.__flag: cls.__flag = obj return cls.__flag # 如果__falg不为None 返回相同的一个对象 obj1 = Mysql.singleton() obj2 = Mysql.singleton() obj3 = Mysql.singleton() print(obj1 is obj2 is obj3)
定制元类实现单例模式
class Mymeta(type): def __init__(self, class_name, class_bases, class_dict): if not class_name.istitle(): # 检查类名的首字母是否大写 不是则报错 raise TypeError("首字母必须大写") if '__doc__' not in class_dict or not class_dict['__doc__'].strip(): # 检测类有没有写注释 raise TypeError("必须写注释") super().__init__(class_name, class_bases, class_dict) self.__flag = None # 记录第一个值 def __call__(cls, *args, **kwargs): # self 为Mysql obj = object.__new__(cls) # 创建一个obj空值 cls.__init__(obj) # 初始化 if not cls.__flag: # 如果为空 cls.__flag = obj # 赋值 return cls.__flag # 如果不为空 赋原来的值 class Mysql(metaclass=Mymeta): """mysql""" __flag = None # obj def __init__(self): self.host = '127.106.0.1' self.port = 8000 def conn(self): pass obj1 = Mysql() # 触发了Mymeta的__call__方法 obj2 = Mysql() obj3 = Mysql() print(obj1 is obj2 is obj3)
第二种方法没有改变对象的创建形式,但是也实现了单例模式。
练习题
练习一:在元类中控制把自定义类的数据属性都变成大写
class Mymetaclass(type): def __new__(cls,name,bases,attrs): update_attrs={} for k,v in attrs.items(): if not callable(v) and not k.startswith('__'): update_attrs[k.upper()]=v else: update_attrs[k]=v return type.__new__(cls,name,bases,update_attrs) class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龙的传人 def walk(self): print('%s is walking' %self.name) print(Chinese.__dict__) ''' {'__module__': '__main__', 'COUNTRY': 'China', 'TAG': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x0000000001E7B950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} '''
练习二:1.元类帮其完成创建对象,以及初始化操作;
2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
3.key作为用户自定义类产生对象的属性,且所有属性变成大写
class Mymetaclass(type): # def __new__(cls,name,bases,attrs): # update_attrs={} # for k,v in attrs.items(): # if not callable(v) and not k.startswith('__'): # update_attrs[k.upper()]=v # else: # update_attrs[k]=v # return type.__new__(cls,name,bases,update_attrs) def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function') obj = object.__new__(self) #创建对象,self为类Foo for k,v in kwargs.items(): # 初始化本质上是将这种对应关系添加到了名称空间中 obj.__dict__[k.upper()]=v return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龙的传人 def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__)
领域模型
领域模型,顾名思义,就是需求所涉及的领域的一个建模,更通俗的讲法是业务模型。
从这个定义我们可以看出,领域模型有两个主要的作用:
- 发掘重要的业务领域概念
- 建立业务领域概念之间的关系
归纳一下域建模的方法就是“从用例中找名词”。 当然,找到名词后,为了能够更加符合面向对象的要求和特点,我们还需要对这些名词进一步完善,这就 是接下来的步骤:加属性,连关系!
最后我们总结出领域建模的三字经方法:找名词、加属性、连关系。
具体信息参考金角大王博客:https://www.cnblogs.com/alex3714/articles/5188179.html
异常处理
异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下。
错误分两种:
1.语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正)
2.逻辑错误
异常的种类
常见异常
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x IOError 输入/输出异常;基本上是无法打开文件 ImportError 无法引入模块或包;基本上是路径问题或名称错误 IndentationError 语法错误(的子类) ;代码没有正确对齐 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] KeyError 试图访问字典里不存在的键 KeyboardInterrupt Ctrl+C被按下 NameError 使用一个还未被赋予对象的变量 SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了) TypeError 传入对象类型与要求的不符合 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 导致你以为正在访问它 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
异常处理
为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理。
如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防。
AGE=10 while True: age=input('>>: ').strip() if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的 age=int(age) if age == AGE: print('you got it') break
如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
#基本语法为 try: 被检测的代码块 except 异常类型: try中一旦检测到异常,就执行这个位置的逻辑 #举例 try: f=open('a.txt') g=(line.strip() for line in f) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) except StopIteration: f.close()
try...except详细用法
1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理
s1 = 'hello' try: int(s1) except IndexError as e: # 未捕获到异常,程序直接报错 print e
2.多分支
s1 = 'hello' try: int(s1) except IndexError as e: print(e) except KeyError as e: print(e) except ValueError as e: print(e)
3.万能异常Exception
s1 = 'hello' try: int(s1) except Exception as e: print(e)
4.也可以在多分支后来一个Exception
s1 = 'hello' try: int(s1) except IndexError as e: print(e) except KeyError as e: print(e) except ValueError as e: print(e) except Exception as e: print(e)
5.异常的其他机构
s1 = 'hello' try: int(s1) except IndexError as e: print(e) except KeyError as e: print(e) except ValueError as e: print(e) #except Exception as e: # print(e) else: print('try内代码块没有异常则执行我') finally: print('无论异常与否,都会执行该模块,通常是进行清理工作')
6.主动触发异常
try: raise TypeError('类型错误') except Exception as e: print(e)
7.自定义异常
class EgonException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg try: raise EgonException('类型错误') # 相当于实例化 except EgonException as e: print(e) # self.msg
8.断言:assert 条件
assert 1 == 1 # 程序正常执行 assert 1 == 2 # 抛出AssertionError
9.总结try..except
1:把错误处理和真正的工作分开来
2:代码更易组织,更清晰,复杂的工作任务更容易实现;
3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
注:此博客部分参考自路飞学城。