目录
关于python中有关一个子类继承多个父类的实例属性绑定问题
str 、repr 、del 面向对象编程之类的重点双下线方法
RuntimeError: super(): __class__ cell not found
super(Person,self).__init__(name,age,sex)
前言:(八股文)面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。
面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念是类和对象。
开篇:(为了便于我们理解直接上手代码去进行阐述)
现实生活中有多个实体,每个实体有不同的多个属性:最基础的方式,我们可以用字典去包含我们所需要的实体属性:
#可以使用字典是存储个体属性
# ' ':' '---相当于赋值语句
dog1={
'name':'tiger1',
'd_type':'泰迪',
'attack':18,
'year':10
}
#该字典中包含了你所需要的狗的属性
#可以通过上述字典去调用一下方法
def bite(person_obj):
person_obj.life_val-=30
但是现实生活当中我们需要多个 个体去进行调用--所以我们衍生出了用公共函数去进行调用,减少重复代码(也可以说是创建模板也可以使用字典去进行类型匹配,也不必在函数当中去书写攻击力的代码)
def Dog(name,d_type):
dog1 = {
'name': name,
'd_type': d_type,
#'attack': 18,
'year': 10
}
return dog1
#可以将函数中生成的一组值进行返回
#创建实体
d1=Dog('tiger','泰迪')
d2=Dog('贝贝','柯基')
print(d1)
也可以使用字典去进行类型匹配,也不必在函数当中去书写攻击力的代码
#例如:
attack={
'泰迪':1,
'柯基':2
}
#在函数中进行声明判断后即可以随时调用
def Dog(name,d_type):
dog1 = {
'name': name,
'd_type': d_type,
#'attack': 18,
'year': 10
}
if d_type in attack:
dog1["attack"]=attack[d_type]
else:
dog1["attack"]=15
return dog1
d3=Dog('tiger','泰迪')
d4=Dog('贝贝','柯基')
print(d3,d4)
值得我们注意的是:
#在函数中进行声明判断后即可以随时调用
def Dog(name,d_type):
dog1 = {
'name': name,
'd_type': d_type,
#'attack': 18,
'year': 10
}
if d_type in attack:
Dog["attack"]=attack[d_type]
else:
dog1["attack"]=15
return dog1
d3=Dog('tiger','泰迪')
d4=Dog('贝贝','柯基')
print(d3,d4)
会导致:TypeError: 'function' object does not support item assignment
原因就是因为: Dog["attack"]=attack[d_type] 该行代码中的 Dog["attack"]与方法名重复
面向对象的几个核心特征如下:(以下都是八股文,建议直接从我写的代码处进行理解!!)
Class
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法
Object对象
一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同
Encapsulation封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
lnheritance继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
Polymorphism多态
多态是面向对象的重要特性,简单点说:"一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来,再通过这个抽象的事物,与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作,他只要在九点钟的时候说:“开始工作"即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”因为“员工”是一个抽象的事物,只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定
直接上代码吧:
1、什么是类?
def Dog(name,d_type):{
}
该部分其实就是一个类,类中定义了这些对象的都具备的属性、共同的方法
2、什么是对象?
说简单一点,对象就是实实在在的东西!!---实体
d3=Dog('tiger','泰迪')
d4=Dog('贝贝','柯基')
d3,d4相当于现实世界当中实实在在存在的实体,即就叫做对象!!
3、什么叫做实例化?
说简单一点就是模板调用的过程(类的调用过程)就叫做实例化!!--实例化之后产生的东西就叫做对象!
4、什么是封装?
def Dog(name,d_type):
dog1 = {
'name': name,
'd_type': d_type,
#'attack': 18,
'year': 10
}
if d_type in attack:
Dog["attack"]=attack[d_type]
else:
dog1["attack"]=15
return dog1
假设在这里我创建了一个狗类的方法和一个人类的方法,所谓封装可以简单的理解为:狗类的方法人理论上是不运行人去调用的!
多态和继承我会在后续代码中进行详细讲解!!
类的基本语法:
1、为了编程的规范:python中类首字母应该大写
直接就可以在类中定义属性
像下面这种在类中直接定义的属性,叫做类属性、类变量(是所有对象都可以公共共享的一个属性)
类中的方法:第一个参数必须是self(也可以是其他名字)---这里的self代表实例本身
class Dog:
#直接就可以在类中定义属性
# 像下面这种在类中直接定义的属性,叫做类属性、类变量
d_type='泰迪'
def hello(self): #方法:第一个参数必须是self(也可以是其他名字)---这里的self代表实例本身
print('Hello!,I am a',self.d_type)
当类写完之后,我们需要对类进行实例化,将其变成一个真正的实体,对象---python中一切皆对象
当我们实例化一个对象之后,就可以通过对象去调用类中的方法 ''' 1、调用类中的方法: 实例.方法 2、调用类中的属性: 实例.属性 '''(#小写的f代表类属性,大写的F代表普通函数)
#当类写完之后,我们需要对类进行实例化,将其变成一个真正的实体,对象---python中一切皆对象
D1=Dog() #生成了一个实例
D2=Dog()
#当我们实例化一个对象之后,就可以通过对象去调用类中的方法
D1.hello()
'''
1、调用类中的方法: 实例.方法
2、调用类中的属性: 实例.属性
'''
#小写的f代表类属性,大写的F代表普通函数
D1.d_type
问题:我们如何定义对象之间私有的属性(只属于自己的属性)?
答:定义初始化方法又叫做构造方法、构造函数,实例化时进行一些初始化工作(自动进行执行)
在类中定义初始化方法:
def __init__(self,name,age,sex):#初始化方法,构造方法,构造函数,实例化时会自动执行,进行一些初始化工作
#要想把name ,age 两个值,真正的存到实例种去,就要把2个值和实例去进行绑定--self存在意义
self.name=name #绑定参数值到实例中去
self.age=age
self.sex=sex #相当于D.sex=" 雄性"
2、self用什么实际意义?
以上述代码进行举例:
要想把name ,age 两个值,真正的存到实例种去,就要把2个值和实例去进行绑定--self存在意义
#为了编程的规范:python中类首字母应该大写
class Dog:
#直接就可以在类中定义属性
# 像下面这种在类中直接定义的属性,叫做类属性、类变量
d_type='泰迪'
def hello(self): #方法:第一个参数必须是self(也可以是其他名字)---这里的self代表实例本身
print('Hello!,I am a',self.d_type,'My name is',self.name,'I am',self.age,'years!')
def __init__(self,name,age,sex):#初始化方法,构造方法,构造函数,实例化时会自动执行,进行一些初始化工作
#要想把name ,age 两个值,真正的存到实例种去,就要把2个值和实例去进行绑定--self存在意义
self.name=name #绑定参数值到实例中去
self.age=age
self.sex=sex #相当于D.sex=" 雄性"
#当类写完之后,我们需要对类进行实例化,将其变成一个真正的实体,对象---python中一切皆对象
D1=Dog('贝贝',2) #生成了一个实例
D2=Dog('tiger',10)
#当我们实例化一个对象之后,就可以通过对象去调用类中的方法
D1.hello()
'''
1、调用类中的方法: 实例.方法
2、调用类中的属性: 实例.属性
'''
#小写的f代表类属性,大写的F代表普通函数
D1.d_type
D1.sex='雄性'
print(D1.sex)
我们也可以直接在类的外部,运用实例化之后的对象进行创建 D1.sex='雄性' 相当于在初始化方法中添加sex属性,并用self进行实例化绑定
def __init__(self,name,age,sex):#初始化方法,构造方法,构造函数,实例化时会自动执行,进行一些初始化工作
#要想把name ,age 两个值,真正的存到实例种去,就要把2个值和实例去进行绑定--self存在意义
self.name=name #绑定参数值到实例中去
self.age=age
self.sex=sex #相当于D.sex=" 雄性"
实例属性又叫做成员变量
小总结:
类属性,类变量,公共属性-----所有实例共享
实例属性,实例变量,成员变量-------每个实例独享
1、类属性的改变,可以直接在外部进行修改,通过类名进行调用方法修改
2、实例属性,只能通过实例进行调用,而不能通过类进行调用
#类属性的改变,可以直接在外部进行修改,通过类名进行调用方法修改
Dog.d_type='柯基'
print(D1.d_type)
#实例属性,只能通过实例进行调用,而不能通过类进行调用
D1.name='小白'
D1.hello()
(实例属性只存在于实例的内存中,而在类中是无法找到的)
3、什么时候用实例属性?什么时候用类属性?
例如:
class Person:
#nationlity='中国'
def __init__(self,name,age,sex,nationlity):#构造函数
self.name=name
self.age=age
self.sex=sex
self.nationlity=nationlity
'''
放在构造函数当中,每个人的国籍都单独存了一份,但是如果14亿中国人,那么其数据量是非常大的,这样是不符合现实的
这个时候我们就不能将国籍放在构造函数当中去,而是作为一个类属性去进行创建
'''
P1=Person('小贝','18','男','中国')
P1=Person('小王','20','男','中国')
P1=Person('小李','21','男','中国')
放在构造函数当中,每个人的国籍都单独存了一份,但是如果14亿中国人,那么其数据量是非常大的,这样是不符合现实的
这个时候我们就不能将国籍放在构造函数当中去,而是作为一个类属性去进行创建
正确做法:将国籍作为一个类属性去进行创建
class Person:
nationlity='中国'
def __init__(self,name,age,sex):#构造函数
self.name=name
self.age=age
self.sex=sex
'''
放在构造函数当中,每个人的国籍都单独存了一份,但是如果14亿中国人,那么其数据量是非常大的,这样是不符合现实的
这个时候我们就不能将国籍放在构造函数当中去,而是作为一个类属性去进行创建
'''
P1=Person('小贝','18','男')
P1=Person('小王','20','男')
P1=Person('小李','21','男')
4、实例对象去更改自己的属性,会对其他实例对象的国籍有影响吗?
答:并没有任何影响
实例也可以直接去更改属于自己的类属性国籍,对于其他属性并没有任何影响 ---相当于,给P实例创建了一个新的实例属性
class Person:
nationlity='中国'
def __init__(self,name,age,sex):#构造函数
self.name=name
self.age=age
self.sex=sex
'''
放在构造函数当中,每个人的国籍都单独存了一份,但是如果14亿中国人,那么其数据量是非常大的,这样是不符合现实的
这个时候我们就不能将国籍放在构造函数当中去,而是作为一个类属性去进行创建
'''
P1=Person('小贝','18','男')
P2=Person('小王','20','男')
P3=Person('小李','21','男')
print(P1.nationlity)
'''
实例也可以直接去更改属于自己的类属性国籍,对于其他属性并没有任何影响
---相当于,给P实例创建了一个新的实例属性
'''
P1.nationlity='美国'
print(P1.nationlity)
print(P3.nationlity)
类之间的依赖关系:
八股文如下:
依赖关系是最常见的一种关系,是一种使用关系,即一个类的实现(或部分方法的实现)需要另外一个类的协助,所以应尽量避免双向的互相依赖关系;
对象 A 持有对象 B 的引用,对象 A 需要借助对象 B 的协助,假如 A 是一个类,那么 B 可以是局部变量,也可以是类中方法参数,或者对静态方法的调用;(对象 A 可以依赖B,也可以不依赖 B,用到了有 B 的部分方法就依赖了 B)
直接上代码:
首先展示一个简单的类之间的交互:(这里以人狗大战为例)
#类之间的简单交互操作
class Dog:
def __init__(self,name,breed,attack_val):
self.name=name
self.breed=breed
self.attack_val=attack_val
self.life_val=100
def bite(self,person):
#狗可以咬人,从这里传进来一个人的对象
person.life_val-=self.attack_val #狗咬人,人的生命值根据狗的攻击力而下降
print('狗:',self.name,'咬了人:',person.name,self.attack_val,'滴血。人还剩:',person.life_val,'滴血!!')
class Person:
role='person' #人类的角色属性都是人
def __init__(self,name,sex,attack_val):
self.name=name
self.sex=sex
self.attack_val=attack_val
self.life_val= 100
def attack(self,dog):
#人可以攻击狗,从这里传进来的dog是狗对象
#人攻击狗,狗的生命值根据人的攻击力下降
dog.life_val-=self.attack_val
print('人:',self.name,'打了狗:',dog.name,self.attack_val,'滴血。狗还剩:',dog.life_val,'滴血!!')
#类的实例化,类实例化完之后,变成一个真正的实体--对象
d1=Dog('小贝','柯基',10)
d2=Dog('小金','金毛',20)
p1=Person('小王','男',30)
#两个对象之间的交互,一般通过类方法进行交互
p1.attack(d1)
d1.bite(p1)
'''
类之间的关系包括:
1、依赖关系:狗和主人的关系
2、关联关系:和我们的同学
3、组合关系:人体器官组合---组合关系比聚合关系还要紧密:人如果死亡,这些器官也会随之死亡
4、聚合关系:电脑配件:如何电脑死了,但是其中的显示器,cpu等都还可以继续操作
5、继承关系:类的三大特性之一:子承父业
'''
#依赖关系:
#狗和主人的关系,可以理解为一种依赖关系,如果没有主人,狗就可能变成流量狗,可能会死
接下来展示如何实现类之间的依赖关系?
#依赖关系:
#狗和主人的关系,可以理解为一种依赖关系,如果没有主人,狗就可能变成流量狗,可能会死
class Dog:
def __init__(self,name,sex,age,master):
self.name=name
self.sex=sex
self.age=age
self.master=master #这里需要注意的是绑定的是键盘输入的实例,这里的master实例是对象,将人的属性绑定到狗类的实例当中
def sayhi(self):
print('hello,my name is %s,my age is %s,%s is my master!!'%(self.name,self.age,self.master.name))
class Person:
def __init__(self,name,sex,age):
self.name=name
self.sex=sex
self.age=age
'''
一般在构造函数中绑定另一个类的对象可以称之为依赖关系--只能够单向绑定,不能够双向绑定!也就是说只能够一个依赖另一个,不能够双向依赖
双向依赖一般都代表着 关联关系!!
'''
p1=Person('小王','20','11')
#在狗的实例化当中将对象p1传进d1当中,这种方式就叫做依赖关系
d1=Dog('小贝','公狗','5',p1)
d1.sayhi()
注解:在这里我们可以看到将人的对象传入狗类的构造函数的master实例属性中去,实现了单向的绑定操作,这种操作也称之为依赖关系!!
一般在构造函数中绑定另一个类的对象可以称之为依赖关系--只能够单向绑定,不能够双向绑定!也就是说只能够一个依赖另一个,不能够双向依赖
双向依赖一般都代表着 关联关系!!
(1)类B以参数的形式传入类A的方法。我个人将它就取名为“参数依赖”。
(2)类B以局部变量的形式存在于类A的方法中。我个人将它就取名为“局部依赖”。
(3)类A调用类B的静态属性或方法。我个人将它就取名为“静态依赖”。
UML图中实现使用一条带有箭头的虚线指向被依赖的类
面向对象编程当中的关联关系
有关面对对象关联的文字解释如下:
关联关系
概念
对象和对象之间的连接。类A关联类B的意思是,如果实例化一个A类的对象,同时会有一个B类的对象被实例化。也就是说,B作为A的属性存在。在Java中,关联关系的代码表现形式为一个类作为另一个类的属性类型存在。 是一种结构关系,说明一个事物的对象与另一个事物的对象相联系。给定有关联的两个类,可以从一个类的对象得到另一个类的对象。 两个类之间的简单关联表示了两个同等地位类之间的结构关系。当要表示结构化关系时使用关联。
定义
A类关联B类,指的是B类对象作为A类的属性存在,称为"has-a"关联关系。
生命周期
如果A类关联B类,那么创建A类的对象时实例化B类的对象,直到A类被销毁,所关联的B类对象也被销毁。即只要A类对象存在,B类对象就存在。
分类
关联关系分为单向关联和双向关联
单向关联:A类关联B类
双向关联:A类关联B类,B类关联A类
关联还有一对一关联和一对多关联
特点
如果两个互相关联的类中有整体和部分的关系,关联关系分为:聚合和组合,主要区别在于生命周期不同。
下面直接上代码来演示到底什么叫做关联以及如何构造一个具有关联关系的类?
简单关联方法:利用同一个类当中的构造函数--可以通过双向绑定进行简单的关联,将两个人关联在一起
'''
1、简单关联方法:利用同一个类当中的构造函数--可以通过双向绑定进行简单的关联,将两个人关联在一起
'''
class Person:
def __init__(self,name,sex,age):
self.name=name
self.age=age
self.sex=sex
self.partener=None #要明确知道这个partener传进去的是一个对象
def privote_behivaor(self):
pass
#关联一般都是进行双向绑定,双向关联
p1=Person('小红','男',11)
p2=Person('小李','女',15)
p1.partener=p2
p2.partener=p1
print(p1.partener.name,p2.partener.name)
#当解除关联关系的时候,也必须进行双向解除关联
p1.partener=None
p2.partener=None
print(p1.partener,p2.partener)
'''
这种方式只是一个低级的双向绑定方式,在写代码的过程中很容易使得少写一个绑定方式,使得变成单向的依赖关系!!
#关联一般都是进行双向绑定,双向关联
p1=Person('小红','男',11)
p2=Person('小李','女',15)
p1.partener=p2
p2.partener=p1
可以看到必须写两行代码对p1,p2进行双向的绑定和关联操作
这种方式只是一个低级的双向绑定方式,在写代码的过程中很容易少写一个绑定方式,使得变成单向的依赖关系!!
下面介绍一种同步的双向绑定方法: 单独设置一种类将其关联在一起!!
'''
2、下面介绍一种同步的双向绑定方法:
单独设置一种类将其关联在一起!!
'''
class Relationship:
def __init__(self):
self.couple=[]
def make_relationship(self,obj1,obj2):
#obj1 和 obj2 都作为对象传到这个方法当中去
self.couple=[obj1,obj2]
print('%s和%s成为一对!!'%(obj1.name,obj2.name))
def get_relationship(self,obj):
print('寻找%s的对象:'%obj.name)
for i in self.couple:
if i != obj:
return i.name
else:
print("他没有对象!!!")
def delete_relationship(self):
print('%s和%s的情侣关系已解除!!'%(self.couple[0].name,self.couple[1].name))
self.couple.clear()
class Person:
def __init__(self,name,sex,age,relation):
self.name=name
self.age=age
self.sex=sex
self.relation=relation #在每个人的实例当中存储关系对象
def privote_behivaor(self):
pass
relation=Relationship()
p1=Person('小李','男',21,relation)
p2=Person('小张','女',22,relation)
relation.make_relationship(p1,p2)
print(p1.relation.couple)
'''
若需要打印名字,由于couple是列表类型,可以单独设置一个方法进行循环获取名字
'''
print(p1.relation.get_relationship(p1))
p1.relation.delete_relationship()
print(p2.relation.get_relationship(p2))
def make_relationship(self,obj1,obj2):
#obj1 和 obj2 都作为对象传到这个方法当中去
self.couple=[obj1,obj2]
print('%s和%s成为一对!!'%(obj1.name,obj2.name))
设置一个列表单独将对象1和对象2结合在一起,值得主注意的是要是想输出这个列表,需要用循环语句去进行遍历操作!!
接下里分析两个常见的错误内容:
① Error:'int' object is not callable:
错误原因: 变量名和函数名写重复
当这两个名称重复时,程序会默认调用Int型对象,但Int对象没有什么调用可言,就爆出了这个错误
解决方法也很简单,要么更改变量名,要么更改方法名。
②执行代码的时候,输出的结果中含有一个“None”
错误原因: 因为python中print函数需要返回值,如果你在print函数中所放的函数没有返回值,那么print将会return None
简单点来说就是你的print(x)当中的x为空值,所以会返回一个none,注意有时候x是一个函数的时候已经函数x中的print已经输出完了,你又对x进行外面加一个print就经常会出现None
面向对象编程当中的组合关系
首先还是一如既往的八股文(其实看了你也不一定会敲代码,但是就是要看):
类的组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。简单说,一 个类中有若干数据成员是其他类的对象。以前我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、 float类型的或结构体类型的,现在我们知道了,数据成员也可以是类类型的。
如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的 一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化
.组合
- 组合是关系当中的最强版本
- 它直接要求包含对象对被包含对象的拥有,即所属关系
- 以及包含对象与被包含对象⽣命期的关系,组合的对象不存在那么被组合的对象也会消失。
- 被包含的对象还可以再被别的对象关联,所以被包含对象是可以共享的,然⽽绝不存在两个包含对象对同⼀个被包含对象的共享。
- 例如:图书和图书的一页; 人和人的心脏
UML类图如下:
说实话这些八股文也就面试用用!
特别简单的来说:
到底什么叫做组合?
答:简单点来说:组合关系 由一堆组件构成一个完整的实体,组件本身独立。但又不能自己运行必须跟宿主组合在一起,才能运行!!
这种方式叫做组合!!
下面多说无益,直接上代码!
'''
简单点来说:组合关系
由一堆组件构成一个完整的实体,组件本身独立。但又不能自己运行必须跟宿主组合在一起,才能运行!!
'''
class Dog:
def __init__(self,name,age,attack_val):
self.name=name
self.age=age
self.attack_val=attack_val
self.life_val=100
print('hello!I am a dog,My name is %s.Dont touch me,my attack is %s'%(self.name,self.attack_val))
def bite(self,person):
person.life_val-=self.attack_val
print('%s 被咬了%s 滴血。还剩%s滴血!!'%(person.name,self.attack_val,person.life_val))
class Person:
def __init__(self,name,age,attack_val):
self.name=name
self.age=age
self.attack_val=attack_val
self.life_val=100
self.weapon=Weapon() #直接与实例绑定,当实例化一个人的时候,自动绑定其拿武器的方法
print('大家好我是%s,我的普通攻击力为%s'%(self.name,self.attack_val))
def attack(self,dog):
dog.life_val-=self.attack_val
print('%s 打了狗%s %s 滴血!狗还剩%s 滴血!'%(self.name,dog.name,self.attack_val,dog.life_val))
class Weapon:
def gun(self,person,attack_val,dog):
self.name='AK47'
self.attack_val=attack_val
dog.life_val-=attack_val
print('人%s拿了武器%s,打了狗%s一下,狗掉了%s滴血,狗还剩%s滴血'%(person.name,self.name,dog.name,self.attack_val,dog.life_val))
def stick(self,person,attack_val,dog):
self.name='打狗棒'
self.attack_val=attack_val
dog.life_val-=attack_val
print('人%s拿了武器%s,打了狗%s一下,狗掉了%s滴血,狗还剩%s滴血'%(person.name,self.name,dog.name,self.attack_val,dog.life_val))
d1=Dog('小贝',11,10)
p1=Person('小李',20,20)
d1.bite(p1)
p1.attack(d1)
p1.weapon.gun(p1,50,d1)
p1.weapon.stick(p1,20,d1)
class Weapon:
def gun(self,person,attack_val,dog):
self.name='AK47'
self.attack_val=attack_val
dog.life_val-=attack_val
print('人%s拿了武器%s,打了狗%s一下,狗掉了%s滴血,狗还剩%s滴血'%(person.name,self.name,dog.name,self.attack_val,dog.life_val))
def stick(self,person,attack_val,dog):
self.name='打狗棒'
self.attack_val=attack_val
dog.life_val-=attack_val
print('人%s拿了武器%s,打了狗%s一下,狗掉了%s滴血,狗还剩%s滴血'%(person.name,self.name,dog.name,self.attack_val,dog.life_val))
这里我单独把武器类给挑出来,可以看到我定义的武器类没有构造函数,自己是无法进行实例化去使用的 。也就是说只能通过人的操作来调用武器,武器类只能作为人的一部分去使用,无法单独使用!!
def __init__(self,name,age,attack_val):
self.name=name
self.age=age
self.attack_val=attack_val
self.life_val=100
self.weapon=Weapon() #直接与实例绑定,当实例化一个人的时候,自动绑定其拿武器的方法
print('大家好我是%s,我的普通攻击力为%s'%(self.name,self.attack_val))
看一下在Person类当中,我把武器类之间绑定到人类的构造方法当中,也就是说,当Person类进行实例化的过程中,武器类自动的绑定到Person类当中,为人所使用了!!
这一节分享两个我遇到的错误:
① IndentationError:expected an indented block
就是代码块缩进错误,找到对于代码块缩进错误的地方,修改即可!!(确实是因为python太敏感了!!!)
②第二个错误更加简单:就是属性确实的问题,self进行绑定就没什么问题了!!
类之间的继承关系:
八股文:
面向对象的编程带来的主要好处之一是代码的重用,实现各种重用的方法之一是通过继承机制。继承完全可以理解成类之间的父类和子类型关系。
继承概念:继承是类与类的一种关系,是一种子类与父类的关系,即爸爸与儿子,爸爸生个儿子,儿子继承爸爸的属性和方法。
如猫类,猫是动物;猫类继承于动物类,动物类为父类也是所有动物的基类;猫类是动物类的子类,也是动物类的派生类。
Python有单继承与多继承。单继承即子类继承于一个类,多继承即子类继承于多个类,多继承会比较少遇到,本章节主要讲单继承。
什么时候使用继承:假如我需要定义几个类,而类与类之间有一些公共的属性和方法,这时我就可以把相同的属性和方法作为基类的成员,而特殊的方法及属性则在本类中定义。这样子类只需要继承基类(父类),子类就可以访问到基类(父类)的属性和方法了,它提高了代码的可扩展性和重用行。
如:猫,狗 都属于动物,它们行为相似性高,都会叫,会吃,会喝,会拉,会撒娇。
Python类继承 注意事项:
- 在继承中基类的构造方法(__init__()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。
- 在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数
- Python 总是首先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)
class Animal:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def eat(self):
print('%s正在吃饭!!'%self.name)
#类名后面加括号表示继承!
'''
当子类继承父类的时候,子类Person便可以使用父类Animal中所有的方法和属性!!!
不需要在重新去定义!!
'''
class Person(Animal):
def talk(self):
print('%s正在讲话!!'%self.name)
class Dog(Animal):
def chase(self):
print('%s正在追赶奔跑'%self.name)
p1=Person()
会进行报错!!
原因:当子类继承父类的时候,子类进行实例化一个具体对象,必须也要满足父类所需要的实例属性
问题:如何对父类方法和类属性进行重写?
简单的重写(重构父类中的方法):
直接在子类当中定义一个和父类相同的方法时,就会重写父类当中的方法
重写(重构父类中的类属性):
同样的,直接在子类当中定义一个和父类相同的类属性时,就会重写父类当中的方法
class Animal:
bu_animal='食肉动物'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def eat(self):
print('%s正在吃饭!!'%self.name)
#类名后面加括号表示继承!
'''
当子类继承父类的时候,子类Person便可以使用父类Animal中所有的方法和属性!!!
不需要在重新去定义!!
'''
class Person(Animal):
bu_animal = '杂食动物'
def talk(self):
print('%s正在讲话!!'%self.name)
'''
简单的重写(重构父类中的方法):
直接在子类当中定义一个和父类相同的方法时,就会重写父类当中的方法
重写(重构父类中的类属性):
同样的,直接在子类当中定义一个和父类相同的类属性时,就会重写父类当中的方法
'''
def eat(self):
print('%s在优雅的进行吃饭'%self.name)
class Dog(Animal):
def chase(self):
print('%s正在追赶奔跑'%self.name)
'''
1、当子类继承父类的时候,子类进行实例化一个具体对象,必须也要满足父类所需要的实例属性
2、当调用方法的时候,也可以随意调用父类中的方法
'''
p1=Person('小李',20,'男')
d1=Dog('小贝',2,'公狗')
p1.eat()
d1.eat()
p1.talk()
d1.chase()
print(p1.bu_animal)
有关类继承中的各种易错点,和重写方法和属性的关键点
问题1:如何让子类不完全重写父类中的方法,即保留了父类中的方法,又对父类中的方法做了补充?
答:执行完父类中的方法,在继续执行子类中的方法!相当于在父类的基础上,添加功能!!
简单点来说只需要在子类重写的方法中调用一下父类中的方法并且在括号中传入self即可!!
多说无益:下面直接上代码!!
class Animal:
bu_animal='食肉动物'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def eat(self):
print('%s正在吃饭!!'%self.name)
#类名后面加括号表示继承!
'''
当子类继承父类的时候,子类Person便可以使用父类Animal中所有的方法和属性!!!
不需要在重新去定义!!
'''
class Person(Animal):
bu_animal = '杂食动物'
def talk(self):
print('%s正在讲话!!'%self.name)
'''
简单的重写(重构父类中的方法):
直接在子类当中定义一个和父类相同的方法时,就会重写父类当中的方法
重写(重构父类中的类属性):
同样的,直接在子类当中定义一个和父类相同的类属性时,就会重写父类当中的方法
'''
def eat(self):
#Animal.eat(self)的self是把子类中的self传进去,对其进行绑定!!
Animal.eat(self)
print('%s在优雅的进行吃饭'%self.name)
我将主要代码挑出来!!
def eat(self):
#Animal.eat(self)的self是把子类中的self传进去,对其进行绑定!!
Animal.eat(self)
print('%s在优雅的进行吃饭'%self.name)
可以看到,子类对父类的eat方法进行重写时,调用了父类中的eat方法并将子类的self传进父类的eat方法当中,完成了绑定!这样子类对于父类的重写,即完成了之前的内容,又对父类的方法进行了一定的补充!!
问题2:如何完全重写父类的构造方法?
答:直接在子类中再写一次构造方法即可
class Person(Animal):
def __init__(self,name):
self.name=name
问题3:如何对父类中的构造方法进行补充?
答:同样的在子类构造方法上面调用父类构造方法即可,Animal.__init__(self,name,age,sex)这里的self的传递流程是,键盘输入到子类当中 子类在传入子类的构造方法,在传入父类当中进行绑定!!
注:无论子类怎么对父类的构造方法进行补充:都是先执行父类的构造方法之后,才能够执行子类的构造方法!!
python2左右都是用这种方法去重写父类的方法
class Person(Animal):
def __init__(self,name,age,sex,hobbies):
Animal.__init__(self,name,age,sex)
self.hobbies=hobbies
目前python3都是用super()来重写方法
class Person(Animal):
def __init__(self,name,age,sex,hobbies):
#Animal.__init__(self,name,age,sex)
#和上面代码效果是一样的
super(Person,self).__init__(name,age,sex)
self.hobbies=hobbies
再简单点来说就是:二者效果相同
Animal.__init__(self,name,age,sex) 等价于 super(Person,self).__init__(name,age,sex)
But现在用的最多的一种方法是:super().__init__(name,age,sex)
class Person(Animal):
def __init__(self,name,age,sex,hobbies):
#Animal.__init__(self,name,age,sex)
#和上面代码效果是一样的
#super(Person,self).__init__(name,age,sex)
super().__init__(name,age,sex)
self.hobbies=hobbies
以上就是重构父类的构造函数,并且子类对父类的构造函数添加一些新的功能!!
对于类中的函数也是一样:用super().eat()既可以执行父类的方法,又可以重新添加新的方法!!
面向对象编程:类的多继承(单纯多继承和多重多继承)
类的多继承
八股文如下
多继承
OCP原则:多用”继承“,少修改
继承的用途:增强基类,实现多态
多态
在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同变现,就是多态
一个类继承自多个类就是多继承它将具有多个类的特征
多继承弊端
多继承很好的模拟了世界,因为事务很少单一继承,但是舍弃简单,必然引入复杂性,带来了冲突
如同一个孩子继承了来自父母双方的特征,那么到底眼睛像爸爸还是妈妈尼?孩子究竟改像谁多一点尼?
多继承的实现会导致编译器设计的复杂度增加,所以现在很多语言舍弃了类的多继承
C++支持多继承;Java舍弃了多继承
Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法声明,继承者必须实现这些方法,就具有了这些能力,就能干什么
多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在一个类多继承了猫和狗类,猫和狗都有了shout方法,子类究竟继承谁的shout尼?
解决方案:实现了多继承的语言,要解决二义性,深度优先或者广度优先
多继承带来的路径选择问题,究竟继承那个父类的特征尼?
究竟先广度优先,还是深度优先
Python使用MRO(method resolution order) 解决类搜索顺序问题。
经典算法,按照定义从左到右,深度优先策略【比如Python2.2之前,左图的MRO算法,MyClass→D→B→A→C→A】
新式类算法,经典算法的升级,重复的只保留最后一个。【左图MRO是:MyClass→D→B→C→A→object】
C3算法,在类被创建出来的时候,就计算除一个MRO有序列表。【Python3唯一支持的算法,左图中MRO是MyClass→D→B→C→A→object】C3过于复杂,没必要去记,我们只要记住【object.mro(),显示继承的方法,从左到右依次查找】
多继承的缺点
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径
团队协作开发,如果引入多继承,那代码将不可控
不管编程语言是否支持多继承,都应当避免多继承
这么多八股文,看了你也看不懂!还不如我直接上代码!!!
什么C3算法,你看了你也用不到,毕竟是工作,又不是搞科研!!
简单的多继承方式:
class Shengxian:
def fly(self):
print('神仙在飞!!')
class Monkey:
def eat(self):
print('猴子在吃桃子!')
class MonkeyKing(Shengxian,Monkey):
def play_golden_stick(self):
print('孙悟空在玩警棍!!')
m1=MonkeyKing()
m1.eat()
m1.fly()
m1.play_golden_stick()
问题1:当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:多继承一般按照从左到右的顺序进行继承!! ---当子类继承两个父类,同时两个父类有相同的方法时,不做任何修饰的情况下,子类调用方法时通常执行,先继承的父类
直接上代码:
'''
问题1:当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:多继承一般按照从左到右的顺序进行继承!!
---当子类继承两个父类,同时两个父类有相同的方法时,不做任何修饰的情况下,子类调用方法时通常执行,先继承的父类
问题2:多重继承的情况下,当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:有两种查找方法:①深度优先原则 ②广度优先原则
深度优先原则: 从左往右寻找,在从左边继承地方向上寻找
1、经典类
class A:
pass
2、新式类:--加上object就变成了新式类,object其实就是一个基类,所有的类都要继承object类
class B(object):
pass
注:
python2 中,经典类采用的是深度优先查找法,新式类采用的是广度优先
puthon3 中,无论是经典类,还是新式类,都是按广度优先查找
python 2.x 默认都是经典类,只有显示继承了object才是新式类
puyhon 3.x 默认都是显示类 都是广度优先(不纯粹的广度优先!!)---C3算法
'''
class Shengxian:
def fly(self):
print('神仙在飞!!')
def fight(self):
print('神仙在打架!!')
class Monkey:
def eat(self):
print('猴子在吃桃子!')
def fight(self):
print('猴子在打架!!')
class MonkeyKing(Shengxian,Monkey):
def play_golden_stick(self):
print('孙悟空在玩警棍!!')
m1=MonkeyKing()
m1.eat()
m1.fly()
m1.play_golden_stick()
m1.fight()
问题2:多重继承的情况下,当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:有两种查找方法:①深度优先原则 ②广度优先原则
深度优先原则: 从左往右寻找,在从左边继承地方向上寻找
多说无益,直接上代码!!!
'''
问题1:当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:多继承一般按照从左到右的顺序进行继承!!
---当子类继承两个父类,同时两个父类有相同的方法时,不做任何修饰的情况下,子类调用方法时通常执行,先继承的父类
问题2:多重继承的情况下,当多个父类中有两个相同方法时,子类调用时会调用哪个方法?
答:有两种查找方法:①深度优先原则 ②广度优先原则
深度优先原则: 从左往右寻找,在从左边继承地方向上寻找
1、经典类
class A:
pass
2、新式类:--加上object就变成了新式类,object其实就是一个基类,所有的类都要继承object类
class B(object):
pass
注:
python2 中,经典类采用的是深度优先查找法,新式类采用的是广度优先
puthon3 中,无论是经典类,还是新式类,都是按广度优先查找
python 2.x 默认都是经典类,只有显示继承了object才是新式类
puyhon 3.x 默认都是显示类 都是广度优先(不纯粹的广度优先!!)---C3算法
'''
class ShengxianBase:
def fight(self):
print('神仙始祖在打架!!')
class MonkeyBase:
def fight(self):
print('猿猴在打架!!')
class Shengxian(ShengxianBase):
def fly(self):
print('神仙在飞!!')
def fight(self):
print('神仙在打架!!')
class Monkey(MonkeyBase):
def eat(self):
print('猴子在吃桃子!')
def fight(self):
print('猴子在打架!!')
class MonkeyKing(Shengxian,Monkey):
def play_golden_stick(self):
print('孙悟空在玩警棍!!')
m1=MonkeyKing()
m1.eat()
m1.fly()
m1.play_golden_stick()
m1.fight()
注:
1、经典类
class A:
pass
2、新式类:--加上object就变成了新式类,object其实就是一个基类,所有的类都要继承object类
class B(object):
pass
注:
python2 中,经典类采用的是深度优先查找法,新式类采用的是广度优先
puthon3 中,无论是经典类,还是新式类,都是按广度优先查找
python 2.x 默认都是经典类,只有显示继承了object才是新式类
puyhon 3.x 默认都是显示类 都是广度优先(不纯粹的广度优先!!)---C3算法
面向对象的三大特性之类的封装
正常八股文如下:
封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象就可以完成自己的操作。使用封装有三大好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以*修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
俺还是同样的话:说这么多也没啥用,你不会完全懂得到底什么叫做封装,让我们直接上代码吧!!!
'''
封装最重要的意义:
防止该类的代码和数据被外部类定义的代码随机访问
1、私有变量:将实例变量变成私有变量
方法:加两个下划线__
注:私有变量在类的内部能够访问,在类的外部是不能够访问的
问题1:如果想让外部能够访问咋办?
答:通过内部定义方法,然后外部调用这个方法进行访问。外部能够变相的访问到私有属性,但是外部不能够修改私有属性的值!
修改值干嘛的,都是要通过内部的方法去操作
2、私有方法:也可以把方法变成私有方法,
同样在方法名前面加两个下划线__
注:私有方法在类的内部能够访问,在类的外部是不能够访问的
问题2:如果想让外部能够访问咋办?
答:通过内部定义方法,然后外部调用这个方法进行访问。外部能够变相的访问到私有方法,但是外部不能够修改私有方法!
修改值干嘛的,都是要通过内部的方法去操作
3、问题3:外部非要直接访问怎么办?(下面这种方法也可以进行修改 私有属性的值!!)
答:实例名._(下划线)+类名
4、问题4:能否在外部进行私有属性的创立
答:一般情况下是不能,只有在实例的创建过程中,创建私有属性才能够创建成功
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
self.__live_value=100
self.__sex='女'
def get_value(self):
print('%s的生命值为%s'%(self.name,self.__live_value))
def modify_value(self):
self.__live_value-=10
print('%s的生命值被修改,减少了十点为:%s'%(self.name,self.__live_value))
def __private_age(self):
print('真实的性别为:%s'%self.__sex)
p1=Person('小李',11)
#只能通过类内部的方法才能够访问私有属性的值
p1.get_value()
#只能通过类的内部方法才能修改私有属性的值
p1.modify_value()
#私有方法外部访问方式
p1._Person__private_age()
#私有属性的外部访问方式
print(p1._Person__live_value)
封装最重要的意义:
防止该类的代码和数据被外部类定义的代码随机访问
1、私有变量:将实例变量变成私有变量
方法:加两个下划线__
注:私有变量在类的内部能够访问,在类的外部是不能够访问的
问题1:如果想让外部能够访问咋办?
答:通过内部定义方法,然后外部调用这个方法进行访问。外部能够变相的访问到私有属性,但是外部不能够修改私有属性的值!
修改值干嘛的,都是要通过内部的方法去操作
2、私有方法:也可以把方法变成私有方法,
同样在方法名前面加两个下划线__
注:私有方法在类的内部能够访问,在类的外部是不能够直接访问的
问题2:如果想让外部能够访问咋办?
答:通过内部定义方法,然后外部调用这个方法进行访问。外部能够变相的访问到私有方法,但是外部不能够修改私有方法!
修改值干嘛的,都是要通过内部的方法去操作
3、问题3:外部非要直接访问怎么办?(下面这种方法也可以进行修改 私有属性的值!!)
答:实例名._(下划线)+类名
4、问题4:能否在外部进行私有属性的创立
答:一般情况下是不能,只有在实例的创建过程中,创建私有属性才能够创建成功
三大特性之类的多态(普通函数如何进行多态)
还是熟悉的八股文:
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作。
多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”。
多态可分为变量多态与函数多态。变量多态是指:基类型的变量(对于C++是引用或指针)可以被赋值基类型对象,也可以被赋值派生类型的对象。函数多态是指,相同的函数调用界面(函数名与实参表),传送给一个对象变量,可以有不同的行为,这视该对象变量所指向的对象类型而定。因此,变量多态是函数多态的基础。
多态还可分为:
-
动态多态(dynamic polymorphism):通过类继承机制和虚函数机制生效于运行期。可以优雅地处理异质对象集合,只要其共同的基类定义了虚函数的接口。也被称为子类型多态(Subtype polymorphism)或包含多态(inclusion polymorphism)。在面向对象程序设计中,这被直接称为多态。
-
静态多态(static polymorphism):模板也允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为“静态”。可以用来实现类型安全、运行高效的同质对象集合操作。C++STL不采用动态多态来实现就是个例子。
对于C++语言,带变量的宏和函数重载(function overload)机制也允许将不同的特殊行为和单个泛化记号相关联。然而,习惯上并不将这种函数多态(function polymorphism)、宏多态(macro polymorphism)展现出来的行为称为多态(或静态多态),否则就连C语言也具有宏多态了。谈及多态时,默认就是指动态多态,而静态多态则是指基于模板的多态。
多说无益,看了你也记不住!!直接上代码:
'''
多态:简单点来说就是多个对象公用一个接口,又表现的形态不一样的现象就叫做多态!!
'''
class Dog:
def sound(self):
print('狗在汪汪叫!!')
class Cat:
def sound(self):
print('猫在喵喵叫!')
# Make_sound 函数相当于一个接口方法!多个对象调用同一个接口,表现出不同的形式,叫做多态!!
def Make_sound(animal_soud):
animal_soud.sound()
d1=Dog()
c1=Cat()
#多个对象调用同一个接口,表现出不同的形式!!
Make_sound(d1)
Make_sound(c1)
'''
一般多态都是抽象类来实现多态!!---抽象类实现多态
'''
多态:简单点来说就是多个对象公用一个接口,又表现的形态不一样的现象就叫做多态!!
在这里我构造的函数当中Make_sound 函数相当于一个接口方法!多个对象调用同一个接口,表现出不同的形式,叫做多态!!
# Make_sound 函数相当于一个接口方法!多个对象调用同一个接口,表现出不同的形式,叫做多态!!
def Make_sound(animal_soud):
animal_soud.sound()
三大特性之类的多态2(抽象类如何进行和实现多态)
八股文如下:
什么是抽象类?
抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
与java一样,python也有抽象类的概念。抽象类是一种特殊的类,它只能有抽象方法,不能被实例化,在子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。
抽象类的作用
抽象类可以实现多个子类*用的部分,而不需要重复写到实现类中。
从设计角度去看,抽象类是基于类抽象而来的,是实现类的基类。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且实现子类必须继承并实现抽象类的方法。
注:抽象方法不需要写方法体,但并不是不能写。可以写,但是没有用。
抽象类中可以定义属性、类方法、静态方法
继承了抽象类,就必须重写抽象类中的抽象方法,否则无法实例化对象,并抛异常。
应该尽量避免多继承
如果出现多继承,那么对于同名抽象方法,哪个继承类靠前就是实现的哪个类的抽象方法。
这对于抽象方法来说没什么意义。
但是!
由于抽象类不止能写抽象方法,还可以写属性、类方法、静态方法、普通方法,这些方法如果同名,就是按继承顺序来继承和重写的了
大概了解了什么是抽象类之后,我用简单的方法去描述如何进行抽象类的多态!!代码如下:
'''
什么是抽象类?
抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
与java一样,python也有抽象类的概念。抽象类是一种特殊的类,它只能有抽象方法,不能被实例化,在子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。
抽象类的作用
抽象类可以实现多个子类*用的部分,而不需要重复写到实现类中。
从设计角度去看,抽象类是基于类抽象而来的,是实现类的基类。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且实现子类必须继承并实现抽象类的方法。
注:抽象方法不需要写方法体,但并不是不能写。可以写,但是没有用。
抽象类中可以定义属性、类方法、静态方法
继承了抽象类,就必须重写抽象类中的抽象方法,否则无法实例化对象,并抛异常。
应该尽量避免多继承
如果出现多继承,那么对于同名抽象方法,哪个继承类靠前就是实现的哪个类的抽象方法。
这对于抽象方法来说没什么意义。
但是!
由于抽象类不止能写抽象方法,还可以写属性、类方法、静态方法、普通方法,这些方法如果同名,就是按继承顺序来继承和重写的了
'''
#定义一个抽象类
class Doucument:
def __init__(self,name):
self.name=name
def show(self):
raise NotImplementedError('Subclass must implement abstract method!!')
#继承抽象类
class Pdf(Doucument):
def show(self):
print('Show pdf contents!')
class Word(Doucument):
def show(self):
print('Show exl contents!!')
print(self.name)
p1=Pdf('联系方式')
p1.show()
w1=Word('成绩单')
#w1.__init__('11')
w1.show()
在这里我定义了一个抽象类 Doucument:中有show方法,但是其子类show方法进行了重构!
#定义一个抽象类
class Doucument:
def __init__(self,name):
self.name=name
def show(self):
raise NotImplementedError('Subclass must implement abstract method!!')
关于python中有关一个子类继承多个父类的实例属性绑定问题
1.使用未绑定方法逐个调用
2.使用super()函数。注意,这里有个特别要注意的地方,当子类继承于多个父类时,super() 函数只可用于调用第一个父类的构造函数,其余父类的构造函数只能使用未绑定的方式调用。
直接看代码吧!!
class Employee:
def __init__(self, salary):
self.salary = salary
def work(self, *args, **kwargs):
print('普通员工在写代码,工资为:', self.salary)
class Customer:
def __init__(self, favourite, address):
self.favourite = favourite
self.address = address
def info(self):
print('我是一个顾客,我的爱好是:%s,地址是%s' % (self.favourite, self.address))
class Mannager(Employee, Customer):
def __init__(self, salary, favourite, address):
print('Manngaer的构造方法')
# 方法一:用未绑定方法来构造,使用类名直接构造,逐个调用
# Employee.__init__(self,salary)
# Customer.__init__(self,favourite,address)
# 方法二:使用super()和未绑定方法
super().__init__(salary)
# 与上一行代码效果相同
# super(Mannager,self).__init__(salary)
Customer.__init__(self, favourite, address)
m = Mannager(25000, 'it产品', '广州')
m.work()
m.info()
面向对象编程之类方法@classmethod
首先还是我们熟悉的八股文:
-
类方法 就是针对 类对象 定义的方法
- 在 类方法 内部可以直接访问 类属性 或者调用其他的 类方法
语法如下
@classmethod
def 类方法名(cls):
pass
-
类方法需要用 修饰器
@classmethod
来标识,告诉解释器这是一个类方法 -
类方法的 第一个参数 应该是
cls
-
由 哪一个类 调用的方法,方法内的
cls
就是 哪一个类的引用 -
这个参数和 实例方法 的第一个参数是
self
类似 -
提示 使用其他名称也可以,不过习惯使用
cls
-
-
通过 类名. 调用 类方法,调用方法时,不需要传递
cls
参数
在方法内部
-
可以通过
cls.
访问类的属性 -
也可以通过
cls.
调用其他的类方法
示例需求
-
定义一个 工具类
-
每件工具都有自己的
name
需求 —— 在 类 封装一个 show_tool_count
的类方法,输出使用当前这个类,创建的对象个数
@classmethod
def show_tool_count(cls):
"""显示工具对象的总数"""
print("工具对象的总数 %d" % cls.count)
在类方法内部,可以直接使用
cls
访问 类属性 或者 调用类方法
类方法:通过@classmethod装饰器实现
类方法和普通方法的区别:
类方法只能访问类变量,不能访问实例变量
直接看代码:
'''
类方法:通过@classmethod装饰器实现
类方法和普通方法的区别:
类方法只能访问类变量,不能访问实例变量
(刨根问底)问题1:为什么类方法中不能够访问实例变量?
答:因为类方法中的self并不是和创建的对象所绑定--简单点来说,因为self这个参数接收的不是实例本身,而是指向类本身
注:当你先写装饰器,在定义方法,会发现方法后的()内是cls 而不是self,这就印证了上面的说明!!
问题2:类方法到底有啥用?
'''
class Student:
def __init__(self,name):
self.name=name
@classmethod
def show(cls):
print('学生的名字为:%s'%cls.name)
s1=Student('小明')
s1.show()
(刨根问底)问题1:为什么类方法中不能够访问实例变量?
答:因为类方法中的self并不是和创建的对象所绑定--简单点来说,因为self这个参数接收的不是实例本身,而是指向类本身
class Student:
name='dog1'
def __init__(self,name):
self.name=name
@classmethod
def show(self):
print(self)
print('学生的名字为:%s'%self.name)
s1=Student('小明')
s1.show()
你会发现类方法中的self指向的是类本身,而不是实例对象
class Student:
name='dog1'
def __init__(self,name):
self.name=name
@classmethod
def show(self):
print(self)
print('学生的名字为:%s'%self.name)
s1=Student('小明')
s1.show()
print(Student)
注:当你先写装饰器,在定义方法,会发现方法后的()内是cls 而不是self,这就印证了上面的说明!!
class Student2:
num=0
def __init__(self,name):
self.name=name
self.num+=1
print(self.name,self.num)
s2=Student2('小李')
s3=Student2('小红')
问题3:为什么num一直是1?
答:因为这里对num+1是针对的每个新创建的实例对象,而不是针对于整个大类而言
问题4:如何解决这个问题?
答:将Self换成类本身即可!!
class Student2:
num=0
def __init__(self,name):
self.name=name
Student2.num+=1
print(self.name,self.num)
s2=Student2('小李')
s3=Student2('小红')
但是这个代码有个bug!当你不创建对象,直接对在外部调用num时也可以使数量+1
class Student2:
num=0
def __init__(self,name):
self.name=name
Student2.num+=1
print(self.name,self.num)
s2=Student2('小李')
s3=Student2('小红')
Student2.num+=1
print(Student2.num)
问题5:如何解决这个问题?
答:可以使用类方法避免这个问题!
class Student2:
#将num变成一个私有变量
__num=0
def __init__(self,name):
self.name=name
#Student2.num+=1
Student2.gain_num()
print(self.name,self.__num)
@classmethod
def gain_num(cls):
cls.__num+=1
s2=Student2('小李')
s3=Student2('小红')
#Student2.num+=1
print(Student2.num)
最终的代码如下:有小伙伴们找到bug可以和我交流一下!!
class Student2:
#将num变成一个私有变量
__num=0
def __init__(self,name):
self.name=name
#Student2.num+=1
Student2.__gain_num()
print(self.name,self.__num)
@classmethod
def __gain_num(cls):
cls.__num+=1
s2=Student2('小李')
s3=Student2('小红')
面向对象编程之静态方法
熟悉的八股文又来了!!!
C++中,若类的方法前加了static关键字,则该方法称为静态方法,反之为实例方法。静态方法为类所有,可以通过对象来使用,也可以通过类来使用。但一般提倡通过类名来使用,因为静态方法只要定义了类,不必建立类的实例就可使用。静态方法只能调用静态变量。
方法的使用:
静态方法与静态变量一样,属于类本身,而不属于那个类的一个对象。调用一个被定义为static的方法,可以通过在它前面加上这个类的名称,也可以像调用非静态方法一样通过类对象调用。
实例方法必须通过类的实例来使用。实例方法可以使用类的非静态成员,也可以使用类的静态成员。
类的静态方法,静态变量是在类装载的时候装载的。但是要特别注意,类的静态变量是该类的对象所共有的,即是所有对象共享变量。所以建议尽量少用静态变量。尽量在静态方法中使用内部变量。
方法的调用:
静态方法与实例方法唯一不同的,就是静态方法在返回类型前加static关键字。静态方法的调用有两种途径:
(1)通过类的实例对象去调用
调用格式为: 对象名.方法名
(2) 通过类名直接调用
调用格式为: 类名.方法名
方法规则:
我们在使用时要注意:
静态方法只能访问类的静态成员,不能访问类的非静态成员;
非静态方法可以访问类的静态成员,也可以访问类的非静态成员;
静态方法既可以用实例来调用,也可以用类名来调用。
直接看代码:
class Dog:
attack=100
def __init__(self,name):
self.name=name
@staticmethod
def show(obj):
print('狗的名字叫做:%s'%obj.name)
d1=Dog('小贝')
d1.show()
问题1:为什么当变成静态方法之后,实例就不自动往方法中传入self,而是要手动添加传入self?
答:静态方法隔断了它跟类或实例的任何关系。简单点说就是虽然静态方法放在类下面,但是和类没有任何关系!!
class Dog:
attack=100
def __init__(self,name):
self.name=name
@staticmethod
def show(obj):
print('狗的名字叫做:%s'%obj.name)
d1=Dog('小贝')
d1.show(d1)
可以看到当手动传入d1后,方法可以正确的显示!!
问题2:静态方法有啥用?
答:静态方法使用装饰器@staticmethod。
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
面向对象核心编程之反射现象1
八股文如下:
1.什么是反射?
2.反射怎么用?
3.什么情况下使用反射?
一.什么是反射?
1.反射定义:
反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!
2.反射的作用:
动态获得类的信息.
让对象告诉我们相关信息(对象拥有属性,方法,已经对象所属的类等)
3.反射:
Reflection
三.什么情况下使用反射?
我们浏览网页,都是从一个链接跳到另一个链接,那么,我现在有个需求:
1.输入"baidu",进入百度网页
2.输入"QQ",进入QQ网页
不使用反射:
class WebPage:
def baidu(self):
print("www.baidu.com")
def qq(self):
print("www.QQ.com")
web = WebPage()
while 1:
content = input("请输入:")
if content.lower() == "qq":
web.qq()
elif content.lower() == "baidu":
web.baidu()
else:
print("输入有误!")
使用反射:
class WebPage:
def baidu(self):
print("www.baidu.com")
def qq(self):
print("www.QQ.com")
web = WebPage()
while 1:
content = input("请输入:").lower()
if hasattr(web,content):
getattr(web,content)()
else:
print("输入有误!")
总结:上面两段代码我们对比下,如果不使用反射,我要反问很多网站,是不是要写很多的elif 来判断?使用反射后,两句代码就能搞定!
问题1:什么是反射?
答:是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)
----简单的来说,反射就是指可以通过字符串的形式来操作对象的属性
问题2:反射有啥用?
答:其中一个应用场景就是人机交互页面,用户在键盘输入的一般都是字符串形式,传入界面当中,程序去寻找关于字符串的方法,从而去调用该方法!!
直接看代码吧!还是比较easy的!
反射、自省有四个方法:
1、 getattr() 获取
2、 hasattr() 判断
3、 setattr() 赋值
4、 delattr() 删除
实现通过字符串的形式,对对象里面的各种属性进行任意的操作!!
'''
问题1:什么是反射?
答:是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)
----简单的来说,反射就是指可以通过字符串的形式来操作对象的属性
问题2:反射有啥用?
答:其中一个应用场景就是人机交互页面,用户在键盘输入的一般都是字符串形式,传入界面当中,程序去寻找关于字符串的方法,从而去调用该方法!!
'''
'''
反射、自省有四个方法:
1、 getattr() 获取
2、 hasattr() 判断
3、 setattr() 赋值
4、 delattr() 删除
实现通过字符串的形式,对对象里面的各种属性进行任意的操作!!
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def walk(self):
print('%s赶紧走!!'%self.name)
def show(self):
print('%s在展示自己的风采!!'%self.name)
def show1(self):
print('%s在展示自己的风采!!'%self.name)
p1=Person('小李',11)
#字符串获取对象中的实例属性
a=getattr(p1,'name')
print(a)
#字符串判断对象中是否存在其方法或者实例属性
if hasattr(p1,'age'):
print('对象p1中存在age')
if hasattr(p1,'run'):
print('p1对象中存在实例属性run')
if hasattr(p1,'walk'):
p1.walk()
if hasattr(p1,'run'):
p1.run()
#赋值,多加属性,或者方法
setattr(p1,'sex','man')
print(p1.sex)
setattr(Person,'show',show)
p1.show()
setattr(p1,'show1',show1)
p1.show1(p1)
#删除属性,或者方法
#delattr(p1,'show')
delattr(p1,'age')
p1.age
面向对象编程之属性方法
八股文如下:
property属性介绍
property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用。当修改值或者设置值的时候只需要像属性那样使用。
定义property属性有两种方式
- 装饰器方式
- 类属性方式
1. 使用装饰器方式
class Person(object):
def __init__(self):
self.__age = 0
# 装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法
@property
def age(self):
return self.__age
# 把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法
@age.setter
def age(self, new_age):
if new_age >= 150:
print("输入错误")
else:
self.__age = new_age
# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000
运行结果:
0
100
输入错误
快捷设置:在pycharm解释器中可以使用快捷方式props直接生成并同步修改。
2. 使用类属性方式
下面的方法应该更符合我们的设置习惯,在常规的定义函数方法后再增加一行property的设置即可:
class Person(object):
def __init__(self):
self.__age = 0
def get_age(self):
"""当获取age属性的时候会执行该方法"""
return self.__age
def set_age(self, new_age):
"""当设置age属性的时候会执行该方法"""
if new_age >= 150:
print("输入错误")
else:
self.__age = new_age
# 类属性方式的property属性
age = property(get_age, set_age)
# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000
运行结果:
0
100
输入错误
property的参数说明:
- 第一个参数是获取属性时要执行的方法
- 第二个参数是设置属性时要执行的方法
简单点来说总结为一下几点!!!
属性方法:
1、把一个方法变成一个静态的属性(变量)
2、能够正常的访问类中的所有东西----例:类变量,实例变量等
问题1:属性方法有啥用?
答:纯正的变量不能够执行一些动作,而属性方法可以执行一些动作。--说白了只是调用方式上面是变量的方式
问题2:既然属性方法可以变成变量,那么可以直接修改值吗?
答:不能够直接修改值,只有设置方法后才能够进行修改
问题3:既然属性方法可以变成变量,那么可以直接删除吗?
答:不能够直接修改值,只有设置方法后才能够进行删除
'''
属性方法:
1、把一个方法变成一个静态的属性(变量)
2、能够正常的访问类中的所有东西----例:类变量,实例变量等
问题1:属性方法有啥用?
答:纯正的变量不能够执行一些动作,而属性方法可以执行一些动作。--说白了只是调用方式上面是变量的方式
问题2:既然属性方法可以变成变量,那么可以直接修改值吗?
答:不能够直接修改值,只有设置方法后才能够进行修改
问题2:既然属性方法可以变成变量,那么可以直接删除吗?
答:不能够直接修改值,只有设置方法后才能够进行删除
'''
class Fligth:
def __init__(self,name):
self.name=name
def checking_status(self):
print('飞机正在检查状态当中!!!')
print('飞机的状态为:%s'%self.name)
return self.name
@property
def flight_status(self):
status=self.checking_status()
if status==0:
print('飞机正在停止起飞!!')
elif status==1:
print('飞机正在准备起飞')
elif status==2:
print('飞机正在候机场!!!')
@flight_status.setter
def flight_status(self,status):
print('修改飞机的状态值status!')
self.status=status
@flight_status.deleter
def flight_status(self):
print('正在删除当中')
f1=Fligth(1)
#f1.checking_status()
f1.flight_status
f1.flight_status=0
print(f1.status)
f1.flight_status
del f1.flight_status
f1.flight_status
关于在相同或者不同python文件夹下利用反射
问题1:如何反射,获取在一个 文件夹 下指定对象的属性,方法等
答:举个例子,直接看代码
'''
如何反射,获取在一个 文件夹 下指定对象的属性,方法等
'''
print(__name__)#__main__ 代表模块本身,self
#这里的__name__就代表当前文件的名称!!
if __name__=="__main__": #该if判断语句只会在被别的模块导入的时候发挥作用,被模块导入时就不会被执行,只有在当前python文件下才发挥作用!!
print('hahahhaha')
'''
__name__在当前模块主动执行情况下(不是被导入执行),等价于__main__就表示说是在当前文件下执行,而不是说是另外写一个python文件下执行
在被其他模块导入执行情况下,__name__就等价于模块名,模块名就是说是__name__存在下的文件的名称
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def walk(self):
print('%s赶紧走!!'%self.name)
def show(self):
print('%s在展示自己的风采!!'%self.name)
def show1(self):
print('%s在展示自己的风采!!'%self.name)
p1=Person('小李',11)
#字符串获取对象中的实例属性
a=getattr(p1,'name')
#print(a)
def say_hi():
print('1111')
import sys
#for k,v in sys.modules.items():
# print(k,v)
#__main__ <module '__main__' from 'C:\\Users\\Bedanvance\\PycharmProjects\\pythonProject2\\反射.py'>
#上面代表真正的self代表模块本身!!
print(sys.modules['__main__'])
mod=sys.modules['__main__']
print(mod.p1)#直接打印person的对象
#利用modules可以直接在python文件夹下获取方法,属性等!!
if hasattr(mod,'p1'):
o=getattr(mod,'p1')
#print(o)
'''
通过modules经过反射拿到对象p1和直接打印p1产生的是同样的内存结果
'''
if hasattr(mod,'say_hi'):
o=getattr(mod,'say_hi')
o()
if hasattr(Person,'walk'):
o=getattr(Person,'walk')
o(p1)
print(__name__)#__main__ 代表模块本身,self
#这里的__name__就代表当前文件的名称!!
if __name__=="__main__": #该if判断语句只会在被别的模块导入的时候发挥作用,被模块导入时就不会被执行,只有在当前python文件下才发挥作用!!
print('hahahhaha')
'''
__name__在当前模块主动执行情况下(不是被导入执行),等价于__main__就表示说是在当前文件下执行,而不是说是另外写一个python文件下执行
在被其他模块导入执行情况下,__name__就等价于模块名,模块名就是说是__name__存在下的文件的名称
'''
解释:
直接对__name__进行打印,我们看到的是代表模块本身__main__
问题2:什么叫做模块本身?
答:就是说该python文件就是模块本身
对 if 语句,该语句只会在该模块作为一个导入模块的时候发挥作用,就是说当别的python文件用import进行导入时,该If语句不会被执行,就可以发现该模块不是其本身模块,而是调用过来的模块!!
mod=sys.modules['__main__']
该语句直接调取本模块的本身地址!!!
直接可以使用mod结合反射语法去调用模块中所有的对象和方法
以下不用看:纯纯八股文!!
反射的定义:主要是应用于类的对象上,在运行时,将对象中的属性和方法反射出来。
使用场景:可以动态的向对象中添加属性和方法。也可以动态的调用对象中的方法或者属性。
反射的常用方法:
1.hasaattr(obj,str)
判断输入的str字符串在对象obj中是否存在(属性或方法),存在返回True,否则返回False
2.getattr(obj,str)
将按照输入的str字符串在对象obj中查找。如找到同名属性,则返回该属性;如找到同名方法,则返回方法的引用,
想要调用此方法得使用 getattr(obj,str)()进行调用。
如果未能找到同名的属性或者方法,则抛出异常:AttributeError。
3.setattr(obj,name,value)
name为属性名或者方法名,value为属性值或者方法的引用
1) 动态添加属性。如上
2)动态添加方法。首先定义一个方法。再使用setattr(对象名,想要定义的方法名,所定义方法的方法名)
4.delattr(obj,str)
将你输入的字符串str在对象obj中查找,如找到同名属性或者方法就进行删除
面向对象编程之类的简单双下划线方法
八股文如下:
1. __call__() 方法
对象+() 可以直接调用__call__()方法 , 类似普通函数的调用
class CallTest(object):
def __init__(self):
print('I am __init__')
def __call__(self):
print('I am __call__')
return True
def run(self):
print('I am run')
return True
obj = CallTest()
obj.run() # 调用普通方法 对象.func_name()
obj() # 调用__call__()方法, 直接 对象()
可以看到,obj这个对象被实例化出来,如果要调用__call__方法的话,直接obj(),即可调用并返回结果。obj就类似一个函数地址,obj()即执行这个函数。
2. __init__() 方法
构造函数,在生成对象时调用
__getattr__, __setattr__, __delattr__
1. 调用对象的一个不存在的属性时会触发__getattr__方法
2. 删除对象的一个属性的时候会触发__delattr__方法
3. 设置对象(增加/修改)属性会触发__setattr__方法
设置对象属性和删除对象属性会触发__setattr__ 和 __delattr__ 方法,但要注意的是,在调用这两个方法时,方法内部必须操作类的属性字典,否则会造成无限递归
3. __getattr__() 方法
----调用(获取)对象属性
class Foo:
a = 1
def __getattr__(self, item):
print('run __getattr__')
f = Foo()
print(f.a) # 属性存在,就不会触发__getattr__()方法
# >> 输出: 1
print(f.b) # 只有在使用点调用属性且属性不存在的时候才会触发,并且返回None
4. __delattr__() 方法
----删除对象属性
class Foo:
def __delattr__(self, item):
print('run __delattr__')
# del self.item # 这样会造成无限递归
self.__dict__.pop(item)
f = Foo()
f.a = 3
print(f.__dict__) # >> 输出: {'a': 3}
print(f.a) # >> 输出: 3
del f.a # >> 输出: run __delattr__
print(f.a) # >> 报错: AttributeError: 'Foo' object has no attribute 'a'
5. __setattr__() 方法
----设置属性: 增加对象属性, 修改对象属性
class Foo:
a = 1
def __setattr__(self, key, value):
print("run __setattr__")
f = Foo()
# 没有赋值,什么都不会发生
f.c = 200 # 如果增加类属性, 触发触发__setattr__()方法
# >> 输出: run__setattr__
f.a = 2 # 如果修改类属性, 触发触发__setattr__()方法
# >> 输出: run __setattr__
class Foo:
a = 1
def __init__(self, b):
self.b = b # 赋值属性操作
def __setattr__(self, key, value):
print("run __setattr__")
f = Foo(100) # 如果实例化的时候传入参数进行赋值属性操作, 触发__setattr__()方法
# >> 输出: run __setattr__
设置属性时, 方法内部必须操作类的属性字典
class Foo:
a = 1
def __setattr__(self, key, value):
# self.key = value # 增加/修改类属性,会触发__setattr__()方法,如果这个操作在setattr方法内部,会造成无限递归
self.__dict__[key] = value # 使用这种方法会完成增加/修改类属性的操作
print("run __setattr__")
f = Foo()
f.y = 3 # 增加/修改类属性,调用__setattr__()方法
# >> 输出: run __setattr__
print(f.__dict__)
# >> 输出: {'y': 3}
当我们重写__setattr__()方法后,方法内部如果不进行属性字典的操作,那么除非直接操作属性字典,否则永远无法完成赋值
class Foo:
def __setattr__(self, key, value):
print("run __setattr__")
f = Foo()
f.y = 3 # 设置对象属性,调用__setattr__()方法,而__setattr__()方法什么都没干,所以完成不了对象的设置属性操作
# >> 输出: run __setattr__
print(f.__dict__)
# >> 输出: {}
print(f.y) # 完成不了赋值
# >> 报错: AttributeError: 'Foo' object has no attribute 'y'
理解了__setattr__()方法的原理,我们就可以利用 __setattr__()方法 实现我们自定义的功能
class Foo:
a = 1
dic = {} # 自定义一个空字典
def __setattr__(self, key, value):
self.dic[key] = value
print("run __setattr__")
f = Foo()
f.y = 3
# >> 输出: run __setattr__
print(f.dic) # 给类变量dic添加键值对
# >> 输出: {'y': 3}
print(f.__dict__) # 类属性不发生变化
# >> 输出: {}
一个小示例:
class Foo:
def __init__(self, dic):
self._dic = dic
def __getattr__(self, item):
val = self._dic[item]
if isinstance(val, dict):
a = Foo(val)
return a # 重点: 又返回一个对象
else:
return val
dic = {'k1': 'v1', 'k2': 'v2'}
dic = Foo(dic)
print(dic.k1)
# >>输出: v1
print(dic.k2)
# >>输出: v2
dic = {'k1': {'k2': 'v2'}}
dic = Foo(dic)
print(dic.k1)
# >>输出: 一个对象 <__main__.Foo object at 0x00000000024D7F98>
print(dic.k1.k2) # 对象可以继续点(.)取属性操作
# >>输出: v2
原理:
Foo(dic)实例化一个对象, dic.k1触发__getattr__()方法, val={'k2': 'v2'},当val值为一个字典对象时,if条件成立, 返回一个以val字典为参数的对象,就是说: dic.k1 == Foo({'k2': 'v2'}),这个对象可以继续通过点(.)调用对象的属性,如果有多层嵌套,一直循环下去
接着上面的例子继续:
def v2(arg):
return arg
dic = {'k1': {'k2': v2}}
dic = Foo(dic)
ret = dic.k1.k2(100)
print(ret)
# >> 输出: 100
6. __getattribute__() 方法
长得和__getattr__那么像,那么__getattribute__与之有什么关系呢?
class Foo:
a = 1
def __init__(self, x):
self.x = x
def __getattribute__(self, item):
print('不管属性[%s]是否存在,我都会执行' % item)
f = Foo(100)
print(f.a)
# >>输出: 不管属性[a]是否存在,我都会执行
# >>输出: None
print(f.b)
# >>输出: 不管属性[b]是否存在,我都会执行
# >>输出: None
print(f.x)
# >>输出: 不管属性[x]是否存在,我都会执行
# >>输出: None
当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
class Foo:
def __getattr__(self, item):
print('run __getattr__')
def __getattribute__(self, item):
print('不管属性[%s]是否存在,我都会执行' % item)
# raise AttributeError('啦啦啦啦')
f = Foo()
# print(f.a)
# >>输出: 不管属性[a]是否存在,我都会执行
# >>输出: None
print(f.a) # 打开注释,手动抛错: raise AttributeError('q')
# >>输出: 不管属性[a]是否存在,我都会执行
# >>输出: run __getattr__
# >>输出: None
7. super()
super 的工作原理如下:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
其中 cls 代表类, inst 代表实例, super 函数做了两件事:
1. 获取实例对象 inst 的类的 MRO 列表
2. 查找 cls 在当前 MRO 列表中的 index ,并返回它的下一个类,即 mro[index + 1]
当使用 super(cls, inst) 时, Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类. 可以看出, 事实上 super 函数和父类没有实质性的关联.
正式讲解如下:
'''
1、len方法:
判断长度。
2、hash方法:
python中hash是一个算法函数,又称哈希算法;
主要指把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值,能够应用于“密码”、“文件完整性校验”、“数字签名”等方向。
3、eq方法:
判断两个等号就会触发 ==
4、item方法:
把对象变成一个字典dict,可以像字典一样进行增删改查
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def __len__(self):
print('字符串长度!')
return 2
def __hash__(self):
print('hash值!!')
return 222
def __eq__(self, other):
print(self.name,other.name)
def __getitem__(self, item):
print('get item:',self.__dict__[item])
#增加和修改都可以用setitem方法!!
def __setitem__(self, key, value):
print('set_item!!')
self.__dict__[key]=value
def __delitem__(self, key):
print('删除属性!')
del self.__dict__[key]
def __delattr__(self, item):
print('删除属性方法!!')
self.__dict__.pop(item)
p1=Person('小明','11')
p2=Person('小李','11')
len(p1)
print(hash(p1))
print(p1==p2)
p1['name']
p1['sex']='man'
p1['student']='小李'
print(p1.sex)
del p1['sex']
#print(p1.sex)
del p1.student
print(p1.student)
1、len方法:
判断长度。
2、hash方法:
python中hash是一个算法函数,又称哈希算法;
主要指把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值,能够应用于“密码”、“文件完整性校验”、“数字签名”等方向。
3、eq方法:
判断两个等号就会触发 ==
4、item方法:
把对象变成一个字典dict,可以像字典一样进行增删改查
str 、repr 、del 面向对象编程之类的重点双下线方法
直接看代码和解释!!
当直接调用str 和repr方法时,会发现他们输出的结构都一样,都是输出其本身,并不便于我们去观察!
于是出现了重写str reptr的双下划线方法,使我们可以更加清晰的观察到其中的字符串型的内容!!
class Person:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
#def __str__(self):
#return '%s,%s'%(self.name,self.age)
#def __repr__(self):
#return 'Orginal(%s,%s)'%(self.name,self.sex)
p1=Person('小李',20,'man')
print(p1.__str__())
print(p1.__repr__())
print(p1)
'''
1、str & repr:
这两个方法比较像,均可改变对象的字符串显示格式!!
问题1:什么时候会执行str方法?
答:①调用str方法的时候 ②直接printf的时候
问题2:什么时候会执行repr方法?
答:repr 或者 交互式解释器中调用时-->obj._repr()
如果_str_没有被定义,那么就会使用_repr_来代替输出
注意:这两种方法的返回值return必须是字符串,否则抛出异常
2、del 析构方法(与init作用正好相反,init初始化时,会做一些实例化工作!)
析构方法,当对象在内存中被是方法时,自动触发执行
注:此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行
所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的!!
'''
class Person:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def __str__(self):
return '%s,%s'%(self.name,self.age)
def __repr__(self):
return 'Orginal(%s,%s)'%(self.name,self.sex)
p1=Person('小李',20,'man')
print(p1.__str__())
print(p1.__repr__())
print(p1)
1、str & repr:
这两个方法比较像,均可改变对象的字符串显示格式!!
问题1:什么时候会执行str方法?
答:①调用str方法的时候 ②直接printf的时候
问题2:什么时候会执行repr方法?
答:repr 或者 交互式解释器中调用时-->obj._repr()
如果_str_没有被定义,那么就会使用_repr_来代替输出
注意:这两种方法的返回值return必须是字符串,否则抛出异常
'''
1、str & repr:
这两个方法比较像,均可改变对象的字符串显示格式!!
问题1:什么时候会执行str方法?
答:①调用str方法的时候 ②直接printf的时候
问题2:什么时候会执行repr方法?
答:repr 或者 交互式解释器中调用时-->obj._repr()
如果_str_没有被定义,那么就会使用_repr_来代替输出
注意:这两种方法的返回值return必须是字符串,否则抛出异常
2、del 析构方法(与init作用正好相反,init初始化时,会做一些实例化工作!)
析构方法,当对象在内存中被是方法时,自动触发执行
注:此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行
所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的!!
'''
class Person:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def __del__(self):
print('正在进行析构!!')
def __str__(self):
return '%s,%s'%(self.name,self.age)
def __repr__(self):
return 'Orginal(%s,%s)'%(self.name,self.sex)
p1=Person('小李',20,'man')
print(p1.__str__())
print(p1.__repr__())
print(p1)
我故意将析构函数del放在最前面,我们会发现,无论我们是否执行析构操作,他都会自动去执行这个析构,并且在全部函数执行完毕后,才执行的析构操作!!
故我做出以下总结:
2、del 析构方法(与init作用正好相反,init初始化时,会做一些实例化工作!)
析构方法,当对象在内存中被是方法时,自动触发执行
注:此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行
所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的!!
有关析构函数的八股文如下:
析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。以C++语言为例:析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数
面向对象编程:new方法实现单例模式
直接来看代码,不行说那么多八股文了!累了!!
'''
关于new方法:
实例化的时候首先会执行一个构造函数,比构造函数之前更早执行的是new方法!!
__new__方法在init方法之前执行!!
new方法是自动执行的!!
'''
class Student:
def __init__(self,name,sex):
self.name=name
self.sex=sex
print('11111')
def __new__(cls, *args, **kwargs):
print(cls,args,kwargs)
s1=Student('小李','man')
我们会发现,如果写了一个new方法之后,init方法完全不执行了?
class Student:
def __init__(self,name,sex):
self.name=name
self.sex=sex
print('11111')
def __new__(cls, *args, **kwargs):
# 负责执行_init_函数,init是由new方法来调用的
print(cls,args,kwargs)
s1=Student('小李','man')
s1.name
问题1:为什么写了New方法之后,init方法不执行了?
答:因为New方法负责执行_init_函数,简单点来说init是由new方法来调用的
class Student:
def __init__(self,name,sex):
self.name=name
self.sex=sex
print('11111')
def __new__(cls, *args, **kwargs):
# 负责执行_init_函数,init是由new方法来调用的
print(cls,args,kwargs)
return object.__new__(cls)
s1=Student('小李','man')
s1.name
当重写了object中new的方法之后,必须继承返回重写后的new的方法之后才能够继续使用!!
如果只是重写了new方法不进行返回的话,那么其Init初始化方法也不会执行!!
问题2:那么new方法到底有啥用?
我们需要先了解什么是设计模式?
什么是设计模式?一共有23种设计模式!
设计模式是针对软件开发中经常遇到的一些设计问题,根据基本的设计原则,总结出来的一套实用的解决方案或者设计思路。 可以看到,设计模式是非常偏实际应用的,相比设计原则更加具体、可执行。 因此,在了解设计模式之前,就需要了解一些基本的设计原则。这些原则才是指导我们写出好代码的关键。
设计模式中的单例模式!!!
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
直接上代码吧!更好的理解!
'''
什么是设计模式?
设计模式是针对软件开发中经常遇到的一些设计问题,根据基本的设计原则,总结出来的一套实用的解决方案或者设计思路。
可以看到,设计模式是非常偏实际应用的,相比设计原则更加具体、可执行。
因此,在了解设计模式之前,就需要了解一些基本的设计原则。这些原则才是指导我们写出好代码的关键。
'''
class Printer:
tasks=[]
instance=None
def __init__(self,name):
self.name=name
self.tasks.append(name)
print('需要打印输出的内容格式是%s,添加任务%s到打印机当中,总任务数为%s,等待排名为:%s!'%(self.name,self.name,len(self.tasks),len(self.tasks)))
def __new__(cls, *args, **kwargs):
if cls.instance==None:
#利用New方法进行实例初始化
obj=object.__new__(cls)
cls.instance=obj
return cls.instance
p1=Printer('exl')
p2=Printer('pdf')
p3=Printer('world')
print(p1,p2,p3)
print(p1.name,p2.name,p3.name)
'''
#多次实例化对象之后,只是实例化第一个对象,
其他两次实例化对象的时候只是init它的name并不是真正的生成一个新的实例,生成的实例还是第一个实例!!
'''
多次实例化对象之后,只是实例化第一个对象,其他两次实例化对象的时候只是init它的name并不是真正的生成一个新的实例,生成的实例还是第一个实例!!
RuntimeError: super(): __class__ cell not found
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
p1=Person('小李',12)
print(type(p1))
#对于Person 来说 类的属性都是type,故我们可以使用type去动态的创建一个类
print(type(Person))
def __init__(self,sex,name,age):
#super(Student,self).__init__(name,age)
super().__init__(name,age)
self.sex=sex
Student=type('Student',(Person,),{'role':'student','classs':'english','__init__':__init__})
print(Student)
s1=Student('man','小李',11)
print(s1.sex,s1.name,s1.age,s1.role,s1.classs)
print(type(s1))
print(type(Student))
在这里我动态的创建了一个类,在类的继承时继承父类属性的时候,不停的报错!自定义函数,用最基本的方法进行super进行继承时,是不能够完全进行继承的!!
用简单的super().__init__(name,age)进行继承,因为是动态创建一个类,简单super方法根本无法找到父类和子类!!
所以我们不能只记住一个简单的super方法调用!!
super(Person,self).__init__(name,age,sex)
用该方法,进行调用时,就会完全消除错误,因为你手动的进行子类和父类的寻找就会完全的排除这种错误!!!
贴士:
所以我们要完全记住的,不要听一些老师和博主的话!!光记一个简单的super调用方法!!如果你想要对基础得到质的升华!!就要慢慢积累
def __init__(self,sex,name,age):
#super(Student,self).__init__(name,age)
super().__init__(name,age)
self.sex=sex
更改为:
def __init__(self,sex,name,age):
super(Student,self).__init__(name,age)
self.sex=sex
错误就会完全的消除!!!
继承方法和属性,一共有三种方法!!我在这里就不一一列举了!如果你想要得到质的升华!我希望我们能够共同的记住这些方法!!
面向对象编程之动态创建一个类
八股文如下:
描述
type() 函数如果你只有第一个参数则返回对象的类型,三个参数返回新的类型对象。
isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。
语法
以下是 type() 方法的语法:
type(object) type(name, bases, dict)
参数
- name -- 类的名称。
- bases -- 基类的元组。
- dict -- 字典,类内定义的命名空间变量。
返回值
一个参数返回对象类型, 三个参数,返回新的类型对象。
直接上代码吧!!
首先简单的进行以下总结!!
①类是由type产生的,type是所有类的祖师爷!!
②type方法可以动态的创建一个类
③type=('类名',(继承什么类,继承类2,....),{属性:类变量--key : value})
'''
类是由type产生的,type是所有类的祖师爷!!
type方法可以动态的创建一个类
type=('类名',(继承什么类,继承类2,....),{属性:类变量--key : value})
问题1:如何在type方法中创建一个复杂的变量?
答:可以通过自定义函数,在放入type方法中,即可完成实例属性的创建
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
p1=Person('小李',12)
print(type(p1))
#对于Person 来说 类的属性都是type,故我们可以使用type去动态的创建一个类
print(type(Person))
Student=type('Student',(Person,),{'role':'student','classs':'english'})
print(Student)
s1=Student('man','小李',11)
print(s1.sex,s1.name,s1.age,s1.role,s1.classs)
print(type(s1))
print(type(Student))
问题1:如何在type方法中创建一个复杂的变量?
答:可以通过自定义函数,在放入type方法中,即可完成实例属性的创建
'''
类是由type产生的,type是所有类的祖师爷!!
type方法可以动态的创建一个类
type=('类名',(继承什么类,继承类2,....),{属性:类变量--key : value})
问题1:如何在type方法中创建一个复杂的变量?
答:可以通过自定义函数,在放入type方法中,即可完成实例属性的创建
'''
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
p1=Person('小李',12)
print(type(p1))
#对于Person 来说 类的属性都是type,故我们可以使用type去动态的创建一个类
print(type(Person))
def __init__(self,sex,name,age):
super(Student,self).__init__(name,age)
#super().__init__(name,age)
self.sex=sex
Student=type('Student',(Person,),{'role':'student','classs':'english','__init__':__init__})
print(Student)
s1=Student('man','小李',11)
print(s1.sex,s1.name,s1.age,s1.role,s1.classs)
print(type(s1))
print(type(Student))
面向对象编程之异常处理
八股文如下:
异常处理,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,提供error code 所未能具体的优势。异常处理分离了接收和处理错误代码。这个功能理清了编程者的思绪,也帮助代码增强了可读性,方便了维护者的阅读和理解。 异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。
异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。
异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。
各种编程语言在处理异常方面具有非常显著的不同点(错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束)。某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C语言中的atoi函数(ASCII串到整数的转换)在输入非法时可以返回0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如C的errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。
通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。
从进程的视角,硬件中断相当于可恢复异常,虽然中断一般与程序流本身无关。
从子程序编程者的视角,异常是很有用的一种机制,用于通知外界该子程序不能正常执行。如输入的数据无效(例如除数是0),或所需资源不可用(例如文件丢失)。如果系统没有异常机制,则编程者需要用返回值来标示发生了哪些错误。
特点
1.在应用程序遇到异常情况(如被零除情况或内存不足警告)时,就会产生异常。
2.发生异常时,控制流立即跳转到关联的异常处理程序(如果存在)。
3.如果给定异常没有异常处理程序,则程序将停止执行,并显示一条错误信息。
4.可能导致异常的操作通过 try 关键字来执行。
5.异常处理程序是在异常发生时执行的代码块。在 C# 中,catch 关键字用于定义异常处理程序。
6.程序可以使用 throw 关键字显式地引发异常。
7.异常对象包含有关错误的详细信息,其中包括调用堆栈的状态以及有关错误的文本说明。
8.即使引发了异常,finally 块中的代码也会执行,从而使程序可以释放资源。
简单的进行总结:
异常处理:
简单点来说,把可能会发生的错误,提取在代码里进行捕捉(监测!!),
try:
代码
except Exception(Exception几乎可以捕捉所有错误的鼻祖) as e(e作为一个临时变量,报出错误打印信息!!):
出错后要执行的代码
对于Exception而言,我是完全不推荐的,我们需要记住很多异常,不同的错误使用不同错误的方法!!
强类型错误:只要发生错误,程序立马崩溃,无法显示你自己定义的捕捉错误写下的文字提示!!
缩进、语法错误都是永远抓不到的强类型错误!!
SyntaxError Indentation
在 Python 中,把程序运行时产生错误的情况叫做异常。出现异常情况有很多,常见的异常有以下几种:
AssertionError 断言语句失败(assert 后的条件为假)
①AttributeError 访问的对象属性不存在
②ImportError 无法导入模块或者对象,主要是路径有误或名称错误
③IndentationError 代码没有正确对齐,主要是缩进错误
④IndexError 下标索引超出序列范围
⑤IOError 输入/输出异常,主要是无法打开文件
⑥KeyError 访问字典里不存在的键
⑦NameError 访问一个未声明的变量
⑧OverflowError 数值运算超出最大限制
⑨SyntaxError python语法错误
⑩TabError Tab和空格混用
十一、TypeError 不同类型数据之间的无效操作(传入对象类型与要求的不符合)
十二、ValueError 传入无效的值,即使值的类型是正确的
十三、ZeroDivisionError 除法运算中除数0 或者 取模运算中模数为0
一旦程序发生异常,表明该程序在执行时出现了非正常的情况,无法再执行下去。默认情况下,程序会终止退出。
直接放代码吧!
while True:
try:
num1=int(input('>>:'))
num2=int(input('>>:'))
res=num1+num2
print(res)
except Exception as e:
print(e)
print('请输入数字!!')
while True:
try:
num1=int(input('>>:'))
num2=int(input('>>:'))
res=num1+num2
print(res,name)
except NameError as e:
print(e)
print('有数据没有被定义!!')
except ValueError as e:
print(e)
print('请输入数字!!')
name='小李'
while True:
try:
num1=int(input('>>:'))
num2=int(input('>>:'))
res=num1+num2
print(res,name)
name.check
except NameError as e:
print(e)
print('有数据没有被定义!!')
except AttributeError as e:
print(e)
print('没有该方法或者属性!!')
except ValueError as e:
print(e)
print('请输入数字!!')
面向对象编程之自定义异常处理
自定义异常:
八股文:
实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类。 在编码规范上,一般将自定义异常类的类名命名为 XXXException,其中 XXX 用来代表该异常的作用。 自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。
问题1:当写程序的过程中没有预料到异常,但是在交付给用户的过程中发生异常了怎么办?
答:这时候我们需要用到万能异常Exception!!
其他异常结构:
try:
else:
没发生异常的情况下就走else下面的代码!!
-----用于代码检测,检测代码中没有错误!!
finally:
不管有没有发生异常,该句话一定会执行!!
主动触发异常:
raise +错误类型!!
问题1:为什么会有主动触发异常这种方式存在?
答:有时,程序需要主动抛出异常,因为某些情况下,你需要反馈消息给更上层的调用者,告诉它有一些异常情况发生,
而你抛出异常的地方,没有能力处理它,因此需要向上抛出异常。
这种情况为什么不让系统自己抛出异常呢?
一个原因是上层的调用者本身就希望能够捕获有别于系统异常的自定义异常,二来,有些情况下,程序的逻辑是没有异常的,
但是,从业务角度考虑,的确是一个不寻常的情况,因此需要我们主动抛出异常。
直接上代码!
'''
问题1:当写程序的过程中没有预料到异常,但是在交付给用户的过程中发生异常了怎么办?
答:这时候我们需要用到万能异常Exception!!
其他异常结构:
try:
else:
没发生异常的情况下就走else下面的代码!!
-----用于代码检测,检测代码中没有错误!!
finally:
不管有没有发生异常,该句话一定会执行!!
主动触发异常:
raise +错误类型!!
问题1:为什么会有主动触发异常这种方式存在?
答:有时,程序需要主动抛出异常,因为某些情况下,你需要反馈消息给更上层的调用者,告诉它有一些异常情况发生,
而你抛出异常的地方,没有能力处理它,因此需要向上抛出异常。
这种情况为什么不让系统自己抛出异常呢?
一个原因是上层的调用者本身就希望能够捕获有别于系统异常的自定义异常,二来,有些情况下,程序的逻辑是没有异常的,
但是,从业务角度考虑,的确是一个不寻常的情况,因此需要我们主动抛出异常。
'''
class Youtube(BaseException):
def __init__(self,msg):
self.msg=msg
#为什么这里有一个双下划线方法?我试了一下,不用该双下划线方法可以报错!!
def __str__(self):
return self.msg
# def show(self):
# return self.msg
name='小李'
d=[1,2,3]
while True:
try:
num1=int(input('>>:'))
num2=int(input('>>:'))
res=num1+num2
print(res,name)
raise Youtube('无法连接到该网站!!')
#d[3]
#name.check
except NameError as e:
print(e)
print('有数据没有被定义!!')
except AttributeError as e:
print(e)
print('没有该方法或者属性!!')
except ValueError as e:
print(e)
print('请输入数字!!')
except Youtube as e:
print('无法连接到该网站!')
except Exception as e:
print('万能错误格式:',e)
else:
print('else没有发生异常才会执行!!')
finally:
print('finally无论发不发生异常都会执行finally语句下的内容!!')
#自定义一个异常,需要自定义一个函数该函数需要继承BaseException
总结一下:
自定义异常类应该总是继承自内置的
Exception
类, 或者是继承自那些本身就是从Exception
继承而来的类。 尽管所有类同时也继承自BaseException
,但你不应该使用这个基类来定义新的异常。BaseException
是为系统退出异常而保留的,比如KeyboardInterrupt
或SystemExit
以及其他那些会给应用发送信号而退出的异常。 因此,捕获这些异常本身没什么意义。 这样的话,假如你继承BaseException
可能会导致你的自定义异常不会被捕获而直接发送信号退出程序运行。
在程序中引入自定义异常可以使得你的代码更具可读性,能清晰显示谁应该阅读这个代码。 还有一种设计是将自定义异常通过继承组合起来。在复杂应用程序中, 使用基类来分组各种异常类也是很有用的。它可以让用户捕获一个范围很窄的特定异常
面向对象编程之断言assert
还是熟悉的八股文
断言(assertion)是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果——当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。
使用断言可以创建更稳定、品质更好且 不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。单元测试必须使用断言(Junit/JunitX)。
除了类型检查和单元测试外,断言还提供了一种确定各种特性是否在程序中得到维护的极好的方法。
使用断言使我们向按契约式设计更近了一步。
断言可以有两种形式
1.assert Expression1
2.assert Expression1:Expression2
其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。如果Expression1为假,则抛出一个 AssertionError,这是一个错误,而不是一个异常,也就是说是一个不可控制异常(unchecked Exception),AssertionError由于是错误,所以可以不捕获,但不推荐这样做,因为那样会使你的系统进入不稳定状态。
由于程序员的问题,断言的使用可能会带来副作用 ,例如:
boolean isEnable=false;
//...
assert isEnable=true;
这个断言的副作用是因为它修改了程序中变量的值并且未抛出错误,这样的错误如果不细心的检查是很难发现的。但是同时我们可以根据以上的副作用得到一个有用的特性,根据它来测试断言是否打开。
直接上代码:(很简单就稍微写两段)
'''
assert:
用于判断代码是否符合执行预期!--也可以说是否符合逻辑!!
只有代码的执行结果为真才能正确执行!否则直接报错
问题1:assert到底有啥用?
答:1、主要用作单元测试,调用接口,结合assert做一个自动化脚本,自动的对每个接口都进行测试,测试接口返回的是否是符合预期的结果
2、当别人调用你的接口的时候,你的接口要求他调用时必须传递指定的关键参数,等他传递进来时,你就可以用assert语句判断他传递的参数是否符合预期
'''
d=[1,2,3]
assert len(d)>3
assert:
用于判断代码是否符合执行预期!--也可以说是否符合逻辑!!
只有代码的执行结果为真才能正确执行!否则直接报错
问题1:assert到底有啥用?
答:1、主要用作单元测试,调用接口,结合assert做一个自动化脚本,自动的对每个接口都进行测试,测试接口返回的是否是符合预期的结果
2、当别人调用你的接口的时候,你的接口要求他调用时必须传递指定的关键参数,等他传递进来时,你就可以用assert语句判断他传递的参数是否符合预期
断言语句不是永远会执行,可以屏蔽也可以启用
因此:
1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行
2.断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值