面向对象编程——Object oriented programming,简称oop,是一种程序设计思想,oop把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
面向过程的程序设计的核心是过程,过程即解决问题的步骤,面向过程的设计就好比精心设计一条流水线,考虑周全什么时候处理什么东西
优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接受其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
在python中,所有数据类型都可以视为对象,当然也可以自定义对象,自定义的对象数据类型就是面向对象中的类的概念
优点:解决了程序的扩展性,对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易
缺点:可控性差,无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示
std1 = {'name':'Michael','score':98} std2 = {'name':'Bob','score':81}
而处理学生成绩可以通过函数实现,比如打印学生的成绩
def print_score(std): print('%s:%s'%(std['name'],std['score']))
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性
(property),如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来
class Student(object): def __init__(self,name,score): self.name = name self.score = score def print_score(self): print('%s:%s'%(self.name,self.score))
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法,面向对象的程序写出来就像这样:
bart = Student('Bart Simpson',59) #对象 #对象/实例只有一种作用,属性引用 lisa = Student('Lisa Simpson',87) #对象 bart.print_score() #方法 lisa.print_score() #方法
面向对象的设计思想是从自然界中来的,因为在自然界中,类(class)和实例(instance)的概念是很自然的,class是一种抽象概念,比如我们定义的class——student,
是指学生这个概念,而实例则是一个个具体的student,比如,Bart Simpson和Lisa Simpson是两个具体的student
所以,面向对象的设计思想是抽象出class,根据class创建instance(实例或对象),面向对象的抽象程度又比函数要高,因为一个class既包含数据,又包含操作数据的方法
1.类和实例
面向对象最重要的概念就是类和实例,必须牢记类是抽象的模版,比如student类,而实例是根据类创建出来的一个个具体的‘对象’,每个对象都拥有相同的方法,但各自的
数据可能不同
对象/实例只有一种作用:属性引用
仍以student类为例,在python中,定义类是通过class关键字:
class Student(object): pass #不作任何事情,只起到占位的作用
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object)(也可以是()),表示该类是从那个类继承下来的,继承的概念我们后面再讲,
通常,如果没有合适的继承类,就使用object类,即(),这是所有类最终都会继承的类,每个python类都隐含了一个超类:object类,他是一个非常简单的类定义,
几乎不做任何事情定义好了Student类,就可以根据Student类创建出的Student的实例,创建实例是通过类名+()实现的
class Student(object): pass #不作任何事情,只起到占位的作用 bart = Student() bart #<__main__.Student at 0x4dd0770> Student #__main__.Student
可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类
class Person: #定义一个类 role = 'person'#人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法 print('person is walking...') print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
可以自由给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:
bart.name = 'Bart Simpson' bart.name #'Bart Simpson'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去,通过定义一个特殊的__init__方法,在创建实例的时候,就把
name,score等属性绑上去(或者说是默认的属性)
class Student(object): def __init__(self,name,score): #类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征 self.name = name #每个学生都有自己的名字 self.score = score #每个学生都有自己的成绩
注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身,
所以传给力实例,即对象有了__init__方法,在创建实例的时候,就不能传入空的参数,必须传入与__init__方法匹配的参数,但self不需要传,python解释器自己会把实例变量传进去
bart = Student('Bart Simpson',59) #语法:对象名 = 类名(参数) bart.name #'Bart Simpson' #直接查看属性:对象名.属性名 bart.score #59 #调用方法:对象名.方法名()
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数,除此之外,类的方法和普通函数没有什么区
别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数
我们定义的类的属性存到了那里?有两种方式查看: dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 特殊的类属性: 类名.__name__ #类的名字(字符串) 类名.__doc__ #类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
数据封装
面向对象编程的一个重要特点就是数据封装,在上面的Student类中,meige 实例就拥有各自的name和score这些数据。我们可以通过函数来访问这个函数数据,比如打印
一个学生的成绩:
class Student(object): def __init__(self,name,score): self.name = name #每个学生都有自己的名字 self.score = score #每个学生都有自己的成绩 bart = Student('Bart Simpson',59) def print_score(std): print('%s:%s'%(std.name,std.score)) print_score(bart)
Bart Simpson:59
但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给
封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:
class Student(): def __init__(self,name,score): self.name = name self.score = score def print_score(self): print('%s:%s'%(self.name,self.score)) bart = Student('Bart Simpson',59)
要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:
bart.print_score()
Bart Simpson:59
这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义,这些数据和逻辑被“封装”起来了,调用
很容易,但却不知道内部实现的细节。
封装的另一个好处是可以给Student类增加新的方法,比如get_grade:
class Student(): def __init__(self,name,score): self.name = name self.score = score def get_grade(self): if self.score >=90: return 'A' elif self.score >=60: return 'B' else: return 'C' lisa = Student('Lisa', 99) bart = Student('Bart', 59) lisa.get_grade()
'A'
小结:
类是创建实例的模板,而实例是一个一个具体的对象,各个实例拥有的数据都相互独立,互不影响;
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
与静态语言不同,python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
对象之间的交互
class Person: #定义一个类 role = 'person' #人的角色属性都是人 def __init__(self,name,aggressivity,life_value): self.name = name #每一个角色都有自己的昵称 self.aggressivity = aggressivity #每一个角色都有自己的攻击力 self.life_value = life_value #每一个角色都有自己的生命值 def attack(self,dog): #人可以攻击狗,这里的狗也是一个对象 #人攻击狗,那么狗的生命值就会根据人的攻击力而下降 dog.life_value -=self.aggressivity class Dog: #定义一个狗类 role = 'dog' #狗的角色属性都是狗 def __init__(self,name,breed,aggressivity,life_value): self.name = name #每一只狗都有自己的昵称 self.breed = breed #每一只狗都有自己的品种 self.aggressivity = aggressivity #每一只狗都有自己的攻击力 self.life_value = life_value #每一只狗都有自己的生命值 def bite(self,people): #狗可以咬人,这里的狗也是一个对象 #狗咬人,那么人的生命值就会根据狗的攻击力而下降 people.life_value -=self.aggressivity egg = Person('egon',10,1000) #创造了一个实实在在的人egg ha2 = Dog('二愣子','哈士奇',10,1000) #创造了一直实实在在的狗ha2 print(ha2.life_value) egg.attack(ha2) #等价于egg调用了attack函数,ha2就在people的位置上,之后呢,就相当于ha2.life_value -= self.aggressivity print(ha2.life_value)
from math import pi class Circle: #定义了一个圆形类:提供计算面积(area)和周长(perimeter)的方法 def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi * self.radius circle = Circle(10) #实例化一个圆 area1 = circle.area() #计算圆面积 per1 = circle.perimeter() #计算圆周长 print(area1,per1)
类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
1)静态属性就是直接在类中定义的变量
2)动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的,而类的动态属性是绑定到所有对象的
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中去找,类也找不到就找父类....最后都找不到就抛出异常
面向对象的组合用法
组合指的是,在一个类中以另一个类的对象作为数据属性,称为类的组合
class Weapon: def prick(self, obj): # 这是该装备的主动技能,扎死对方 obj.life_value -= 500 # 假设攻击力是500 return obj.life_value class Person: # 定义一个人类 role = 'person' # 人的角色属性都是人 def __init__(self,name,life_value): self.name = name # 每一个角色都有自己的昵称; self.weapon = Weapon() # 给角色绑定一个武器; self.life_value = life_value egg = Person('egon',100) alex = Person('bbb',100) egg.weapon.prick(alex) #egg组合了一个武器的对象,可以直接egg.weapon来使用组合类中的所有方法 #egg.weapon相当于Weapon() #egg.weapon.prick()相当于调用了Weapon类中的prick方法
圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积,圆环的周长是内部圆的周长加上外部圆的周长
这个时候,我们首先实现一个圆形类,计算一个圆的周长和面积,然后在'环形类'中组合圆形的实例/对象作为自己的属性来用
from math import pi class Circle: #定义一个圆形类,提供计算面积(area)和周长(perimeter)的方法 def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi * self.radius circle = Circle(10) #实例化一个圆 area1 = circle.area() #计算圆面积 per1 = circle.perimeter() #计算圆周长 print(area1,per1) #打印圆面积和周长 class Ring: #定义了一个圆环类,提供圆环的面积和周长的方法 def __init__(self,radius_outside,radius_inside): self.outside_circle = Circle(radius_outside) #对象作为属性 self.inside_circle = Circle(radius_inside) #对象作为属性 def area(self): return self.outside_circle.area() - self.inside_circle.area() def perimeter(self): return self.outside_circle.perimeter() + self.inside_circle.perimeter() ring = Ring(10,5) #实例化一个圆形 print(ring.perimeter()) #计算环形的周长 print(ring.area()) #计算环形的面积
用组合的方式建立了类与组合的类之间的关系,他是一种’有‘的关系,比如教授有生日,教授较python课程
class BirthDate: def __init__(self,year,month,day): self.year = year self.month = month self.day = day class Course: def __init__(self,name,price,period): self.name = name self.price = price self.period = period class Teacher: def __init__(self,name,gender,birth,course): self.name = name self.gender = gender self.birth = birth self.course = course def teach(self): print('teaching') p1 = Teacher('egon','male',BirthDate('1995','1','27'),Course('python','28000','4 months')) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period)
1995 1 27 python 28000 4 months
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
初识面向对象小结:
定义一个类
class Person: # 定义一个人类 role = 'person' # 人的角色属性都是人 def __init__(self, name, aggressivity, life_value, money): self.name = name # 每一个角色都有自己的昵称; self.aggressivity = aggressivity # 每一个角色都有自己的攻击力; self.life_value = life_value # 每一个角色都有自己的生命值; self.money = money def attack(self,dog): # 人可以攻击狗,这里的狗也是一个对象。 # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降 dog.life_value -= self.aggressivity
定义一个狗类
class Dog: # 定义一个狗类 role = 'dog' # 狗的角色属性都是狗 def __init__(self, name, breed, aggressivity, life_value): self.name = name # 每一只狗都有自己的昵称; self.breed = breed # 每一只狗都有自己的品种; self.aggressivity = aggressivity # 每一只狗都有自己的攻击力; self.life_value = life_value # 每一只狗都有自己的生命值; def bite(self,people): # 狗可以咬人,这里的狗也是一个对象。 # 狗咬人,那么人的生命值就会根据狗的攻击力而下降 people.life_value -= self.aggressivity
接下来,又创建一个新的兵器类
class Weapon: def __init__(self,name,price,aggrev,life_value): self.name = name self.price = price self.aggrev = aggrev self.life_value = life_value def update(self,obj): #obj就是要带这个装备的人 obj.money -=self.price #用这个武器的人花钱买所以对应的钱要减少 obj.aggressivity += self.aggrev #带上这个装备可以让人增加攻击 obj.life_value += self.life_value #带上这个装备可以让人增加生命值 def prick(self,obj): #这是该装备的主动技能,扎死对方 obj.life_value -= 500 #假设攻击力是500
测试交互
lance = Weapon('长矛',200,6,100) egg = Person('egon',10,1000,600) #创造了一个实实在在的人egg ha2 = Dog('二愣子','哈士奇',10,1000) #创造了一直实实在在的狗ha2 #egg独自力战二愣子深感吃力,决定穷毕生积蓄买一把武器 if egg.money > lance.price: #如果egg的钱比装备的价格多,可以多买一把长矛 lance.update(egg) #egg花钱买了一个长矛防身,且自身属性得到了提高 #egg.money -= self.price 400 #egg.aggressivity += self.aggrev 16 #egg.life_value += self.life_value 1100 egg.weapon = lance #egg装备上了长矛,将对象给了egg,增加了一个属性 print(ha2.life_value) egg.attack(ha2) #人攻击狗 1000-16 (狗的生命值) print(ha2.life_value) #这句话和print(egg.attack(ha2))等价 egg.weapon.prick(ha2) #lance.prick(ha2) ==> ha2.life_value -= 500 #发动武器技能 print(ha2.life_value) #武器的战斗力是500
2.访问限制
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量来操作数据,这样,就隐藏了内部的复杂逻辑。
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:
bart = Student('Bart Simpson', 59) bart.score = 99 bart.score
99
封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式
封装的好处
1)将变化隔离
2)便于使用
3)提高复用性
4)提高安全性
封装原则
1)将不需要对外提供的内容都隐藏起来
2)把属性都隐藏,提供公共方法对其访问
私有变量和私有方法
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在python中,实例的变量名如果以__开头,就变成了一个私有变量(private),
只有内部可以访问访问,外部不能访问 。即在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
class Student(): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % self.__name, self.__score)
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:
bart = Student('Bart Simpson', 59) bart.__name
------------------ AttributeError Traceback (most recent call last) <ipython-input-137-5ee3f4ef867a> in <module> 1 bart = Student('Bart Simpson', 59) ----> 2 bart.__name AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:
class Student(): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % self.__name, self.__score) def get_name(self): return self.__name def get_score(self): return self.__score
如果又要允许外部代码修改score:
class Student(): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % self.__name, self.__score) def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): self.__score = score
原先直接通过bart.score = 99也可以修改,为何要定义一个方法大费周折?因此在方法中,可以对参数做检查,避免传入无效的参数:
class Student(): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % self.__name, self.__score) def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self,score): if 0<= score <=100: self.__score = score else: raise ValueError('bad score')
需要注意的是,在python中,变量名类似__xxx__的,也就是以双下划线开头,并且双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,
所以,不能用__name__,__score__这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,
意思是:“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为python解释器对外把__name变量改成了_Student__name,所以,
仍然可以通过_Student__name来访问__name变量
class Student(): def __init__(self,name,score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % self.__name, self.__score) bart = Student('ddd', 66) bart._Student__name
'ddd'
但是强烈建议你不要这么干,因为不同版本的python解释器可能会把__name改成不同的变量名。
总的来说就是,python本身没有任何机制阻止你干坏事,一切全靠自觉。
最后,注意下面的这种错误写法:
bart = Student('ddd' , 66) bart.get_name()
'ddd'
bart.__name = 'New Name'#设置__name变量 bart.__name
'New Name'
表面上看,外部代码‘成功’设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被python解释器自动改成了
_Student__name,而外部代码给bart新增了一个__name变量。
bart.get_name() #get_name()返回的是self._name
'ddd'
#其实这仅仅是一种变形操作 #类中所有双下划线开头的名称如__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是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅知识一种语法意义上的变形
这种自动变形的特点:
1)类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果
2)这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的
3)在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下划线开头的属性在继承给子类时,子类是无
法覆盖的。
这种变形需要注意的问题是:
1)这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出敏子:_类名__属性,然后就可以访问了,如_A__N
2)变形的过程中只在类的内部生效,在定义后的赋值操作,不会变形
私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况下 class A: def fa(self): print('from A') def test(self): self.fa() class B(A): def fa(self): print('from B') b = B() b.test() #结果:from B #把fa定义成私有的,即__fa class A: def __fa(self): #在定义时就变形为_A__fa print('from A') def test(self): self.__fa() #只会与自己所在的类为准,即调用_A__fa class B(A): def __fa(self): print('from B') b = B() b.test() #结果:from A
from B from A
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码,而外部使用者只知道一个接口(函数),只要接口(函数)名,参数不变,
使用者的代码永远无需改变,这就提供了一个良好的合作基础,或者说,只要这个接口基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name = name self.owner = owner self.__width = width self.__length = length self.__high = high def tell_area(self):#对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length #使用者 r1 = Room('卧室','egon',20,20,20) r1.tell_area() #使用者调用接口tell_area #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name = name self.owner = owner self.__width = width self.__length = length self.__high = high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求该下列一行就可以简单的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 r1.tell_area()
400 #因为 r1=Room('卧室','egon',20,20,20)未在下面写,所以是400
3.继承和多态
在opp程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类(Base class)或超类
(Super class)
python中的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass(ParentClass): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 SubClass2.__bases__
(__main__.ParentClass1, ) (__main__.ParentClass1, __main__.ParentClass2)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现
ParentClass1.__bases__ ParentClass2.__bases__
(object,)
继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分
抽象分成两个层次:
1)将奥巴马和梅西这两对象比较像的部分抽象成类
2)将人、猪、狗这三个类比较像的部分抽取成父类
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现他,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的内容相同时,我们不可能从头开始写一个类B,
这就用到了类的继承的概念,通过继承的方式新建类B,让B继承A,B会遗传A的所有属性(数据属性和函数属性),实现代码重用
比如我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:
class Animal(): def run(self): print('Animal is running...') a = Animal() a.run()
Animal is running...
当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:
class Dog(Animal): pass class Cat(Animal): pass
对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类,Cat和Dog类似。
继承有什么好处,最大的好处是子类获得了父类的全部功能。由于Animal实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
dog = Dog() dog.run() cat = Cat() cat.run()
Animal is running... Animal is running...
继承的第二个好处需要对我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示
Dog is running...和Cat is running...,因此,对Dog和Cat类改进如下:
class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') dog = Dog() dog.run() cat = Cat() cat.run()
Dog is running... Cat is running...
当子类和父类都存在相同的run()方法时,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性
时,就以自己为准
class A: def hahaha(self): print('A') class B(A): def hahaha(self): super().hahaha() #super(B,self).hahaha() print('B') a = A() b = B() b.hahaha()
class Animal: def __init__(self,name,aggressivity,life_value): self.name = name self.aggressivity = aggressivity self.life_value = life_value def eat(self): print('%s is eating'%self.name) class Dog(Animal): def __init__(self,name,breed,aggressivity,life_value): super().__init__(name,aggressivity,life_value) #执行父类Animal的init方法 self.breed = breed #派出了新的属性 def bite(self,people): people.life_value -= self.aggressivity #派生出了新的技能:狗有咬人的技能 def eat(self): print('from Dog') class Person(Animal): #人类,继承Animal def __init__(self,name,aggressivity,life_value,money): super().__init__(name,aggressivity,life_value) #执行父类的init方法 self.money = money #派生出了新的属性 def attack(self,dog): #派生出了新的技能:人都有攻击的技能 dog.life_value -= self.aggressivity def eat(self): Animal.eat(self) #super().eat() print('from person') egg = Person('egon',10,1000,600) ha2 = Dog('二愣子','哈士奇',10,1000) print(egg.name) print(ha2.name) egg.eat()
egon 二愣子 egon is eating from person
通过继承建立了派生类和基类之间的关系,他是一种"是"的关系,比如白马是马,人是动物
当类之间有很多的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
class Teacher: def __init__(self,name,gender): self.name = name self.gender = gender def teach(self): print('teaching') class Professor(Teacher): pass p1 = Professor('egon','male') p1.teach()
teaching
抽象类和接口类
接口类
继承有两种用途:
1)继承基类的方法,并且做出自己的改变或者扩展(代码重用)
2)声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
实践中,继承的第一种含义意义并不很大,甚至常常是有害的,因为它使得子类与基类出现强耦合
继承的第二种含义非常重要,它又叫“接口继承”
接口继承实质上是要求”做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关系具体细节,可一视同仁的处理实现了特定接口的所有对象“——这在
程序设计设计上,叫做归一化
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好像linux的泛文件概念一样,所有东西都可以当作文件处理,不必管系他是内存、磁盘、
网络还是屏幕
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
接口提取了一群类共同的函数,可以把接口当作一个函数的集合,然后让子类去实现接口中的函数,这么做的意义在于归一化,归一化就是只要是基于同一个接口实现的
类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大的降低
了使用者的使用难度。
比如:我们定义了一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和松鼠
到你面前,即便是你分别不到那只是什么鼠,但肯定知道他们都能跑,都会吃,都能呼吸
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后有本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学
会了怎么开汽车,那么无论是本田、奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,函数调用都一样
抽象类
什么是抽象类
与java一样,python也有抽象类的概念,但是同样需要借助模块来实现,抽象类是一个特殊类,他的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中提取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性
比如我们有香蕉的类,苹果的类,桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子,你永远
无法吃到一个叫做水果的东西
从设计的角度来看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法,这一点与接口有点类似,但其实是
不同的
import abc class All_file(metaclass=abc.ABCMeta): all_type = 'file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): #子类必须定义该功能 pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): #子类必须定义该功能 pass class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class sata(All_file):#子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian = Txt() yingpanwenjian = sata() jinchengwenjian = process() #这样大家都是被归一化了,也就是一切皆文件的意思 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
抽象类与接口类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性
抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念
1)多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承
而在继承接口的时候,我们反而鼓励你多继承接口
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该那些不需要的接口
2)方法的实现
在抽象类中,任何方法都只是一种规范,具体的功能需要子类实现
钻石继承
继承顺序
1)python的类可以继承多个类,java和c#中则只能继承一个类
2)python中的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类
class A(object): def test(self): print('from A') 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): pass f1 = F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线型列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3统一都是新式类
from D (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,如上所示
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止
而这个MRO列表的构造是通过一个C3线性化算法来实现的,实际上就是合并所有父类的MRO列表并循环如下三条准则:
1)子类会先于父类被检查
2)多个父类会根据他们在列表中的顺序被检查
3)如果对下一个类中存在两个合法的选择,选择第一个父类
继承小结
继承的作用
减少代码的重用、提高代码可读性、规范编程模式
几个名词
抽象:抽象即抽取类似或者说比较像的部分,是一个从具体到抽象的部分
继承:子类继承了父类的方法和属性
派生:子类在父类方法和属性的基础上产生了新的方法和属性
抽象类和接口类
1)多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承
而在继承接口的时候,我们反而鼓励你来多继承接口
2)方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
多态
多态指的是一类事物有多种形态
动物有多种形态:人、狗、猪
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod #?????????????????????????/ def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
文件有多种形态:文本文件,可执行文件
import abc class File(metaclass=abc.ABCMeta) :#同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print('open file') class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print('execute file')
要理解什么是多态,我们首先要对数据类型再做一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和python自带的数据类
型,比如:str、list、dict没什么两样。
a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型
判断一个变量是否是某个类型可以用isinstance()判断:
a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型 a = list() isinstance(a,list) isinstance(b,Animal) isinstance(c,Dog)
看来a、b、c确实对应着list、Animal、Dog这3种类型。
但是:
isinstance(c,Animal)
True
看来c不仅仅是Dog,c还是Animal!
因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!
所以,在继承关系中,如果一个实例的数据类型是某个子类,那他的数据类型也可以被看做是父类。但是,反过来就不行。即子类既是子类,又是父类。
要理解多态的好处,我们还需要编写一个函数,这个函数接受一个Animal类型的变量:
class Animal(): def run(self): print('Animal is running...') def run_twice(animal): animal.run() animal.run()
当我们传入Animal的实例时,run_twice()就打印出:
run_twice(Animal())
Animal is running... Animal is running...
当我们传入Dog的实例时,run_twice()就打印出:
class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') run_twice(Dog())
Dog is running... Dog is running...
run_twice(Cat())
Cat is running... Cat is running...
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:
class Tortoise(Animal): def run(self): print('Tortoise is running slowly...')
当我们调用run_twice()时,传入Tortoise的实例:
run_twice(Tortoise())
Tortoise is running slowly... Tortoise is running slowly...
你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的好处就是,当我们需要传入Dog、Cat、Tortoise...时,我们只需要接受Animal类型就可以了。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类
或子类,就会自动调用实际类型的run()方法,这就是多态的意思。
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接受时会产生不同的行为(即方法),也就是说,
每个对象可以用自己的方式去响应共同的消息,所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。比如:老师.下课铃响了(),学生.下课铃响了(),
老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod #?????????????????????????/ def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao') peo = People() dog = Dog() pig = Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑他们三者的具体类型是什么,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
鸭子类型
python崇尚鸭子类型,即”如果看起来像,叫声像而且走起路来像鸭子,那么它就是鸭子“
多态的真正威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的,这就是著名的”开闭“原则。
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice等函数。
小结
继承可以把父类的所有功能都直接拿过来,这样就不必从零做起,子类只需要新增自己特有的方法,也可以把父类不合适的方法覆盖重写。
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
使用type()
首先,我们来判断对象类型,使用type()函数:
基本类型都可以用type()判断
type(123) type('str')
int str
判断一个对象是否是某种类型
h = Husky() isinstance(h, Husky) # True
能用type()判断的基本类型也可以用isinstance()函数:
isinstance('a', str)
可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
isinstance([1, 2, 3], (list, tuple))
使用dir()
如果要获得一个对象的所有属性和方法,可以用dir()函数,它返回一个包含字符串的list。
测试对象的属性:
class MyObject(): def __init__(self): self.x = 9 def power(self): return self.x * self.x obj = MyObject() hasattr(obj,'x') # 有属性‘x’吗 setattr(obj,'y',19) # 设置一个属性‘y’ getattr(obj,'y') # 获取属性‘y’ getattr(obj, 'z') # 获取不存在的属性,抛出ArrtibuteError getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回404 #同理,也可以获取方法
小结:
通过内置的一系列函数,我们可以对任意一个python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息,
如果可以直接填写:
sum = obj.x + obj.y #而不是 sum = getattr(obj, 'x') + getattr(obj, 'y')
类属性,归Student类所有:
class Student(object): name = 'Student'
s = Student() print(s.name) #因为实例并没有name属性,所以查找class的name属性:Student print(Student.name) #打印类的属性:Student s.name = 'Michael' #给实例绑定name属性 print(s.name) #由于实例属性优先级比类属性高,因此,会屏蔽掉类的name属性:Michael print(Student.name) #但是类属性未消失
4.面向对象高级编程
property属性
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显他听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
#成人的BMI数值 #过轻:低于18.5;正常:18.5-23.9;过重:24-27;肥胖:28-32;非常肥胖:高于32 #体质指数(BMI)=体重(kg)/身高方(m) class People: def __init__(self,name,weight,height): self.name = name self.weight = weight self.height = height @property def bmi(self): return self.weight / (self.height**2) p1 = People('egon',75,1.85) print(p1.bmi())
例二:圆的周长和面积
import math class Circle: def __init__(self,radius): #圆的半径radius self.radius = radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return math.pi*self.radius #计算周长 c = Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 #输出结果: 10 314.1592653589793 31.41592653589793
#注意:此时的特征area和perimeter不能被赋值 c.area = 3 #为特征area赋值 #抛出异常:can't set attribute
为什么要用property
将一个类的函数定义为特征之后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下:面向对象的封装有三种方式
public:这种其实就是不封装,是对外公开的
protected:这种封装方式对外不公开,但对朋友或者子类公开
private:这种封装对谁都公开
python并没有在语法上把他们三个内建到自己的class机制中,在C++里一般会将所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通
过property方法可以实现
class Foo: def __init__(self,v): self.__NAME = v #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TyperError('%s must be str'%value) self.__NAME = value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f = Foo('egon') print(f.name) #f.name = 10# 抛出异常’TypeError:10 must be str‘ f.name = 'A' f.name #def f.name #抛出异常’TypeError:Can not delete'
'egon' A
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('set的时候运行我啊%s'%value) @AAA.deleter def AAA(self): print('delete的时候运行我啊%s'%value) #只有在属性AAA定义property后才能定义AAA.setter,AAa.deleter f1 = Foo() f1.AAA f1.AAA = 'aaa' del f1.AAA
get的时候运行我啊 set的时候运行我啊aaa delete的时候运行我啊90
class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA = property(get_AAA,set_AAA,delete_AAA)#内置property三个参数与get,set,delete一一对应 f1 = Foo() f1.AAA f1.AAA = 'aaa' del f1.AAA
get的时候运行我啊 set的时候运行我啊 delete的时候运行我啊
如何用?
class Goods: def __init__(self): #原价 self.original_price = 100 #折扣 self.discount = 0.8 @property def price(self): #实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self): del self.original_price obj = Goods() obj.price #获取商品价格 obj.price = 200 #修改商品原价 print(obj.price) del obj.price #删除商品原价 #我的理解是可以把函数(方法)当作属性来做,自然而来可以许改修改函数,即属性
classmethod
class Classmethod_Demo(): role = 'dog' @classmethod def func(cls): print(cls.role) Classmethod_Demo.func() #结果:dog
staticmethod
class Staticmethod_Demo(): role = 'dog' @staticmethod def func(): print('当普通方法用') Staticmethod_Demo.func() #结果:当普通方法用
class Foo: def func(self): print('in father') class Son(Foo): #继承类 def func(self): print('in son') s = Son() s.func() #结果:in son
class A: __role = 'CHINA' @classmethod def show_role(cls): print(cls.__role) @staticmethod def get_role(): return A.__role @property def role(self): return self.__role a = A() print(a.role) #对应的是property print(a.get_role()) #等价于print(A.get_role()),对应的是@staticmethod a.show_role() #等价于A.show_role() #对应的是@classmethod
isinstance和issubclass
isinstance(objcet,cls)检查是否obj是否是类cls的对象
class Foo(object): pass obj = Foo() isinstance(obj,Foo) #结果:True
issubclass(sub,super)检查sub类是否是super类的派生类,即sub是不是super的子类
class Foo: pass class Bar(Foo): pass issubclass(Bar,Foo)
反射
什么是反射
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。
python面向对象中的反射:通过字符串的形式操作对象相关的属性,python中的一切事物都是属性(都可以使用属性)
四个可以实现自省的函数
def hasattr(*args,**kwargs): #hasattr pass def getattr(object,name,default=None):#getattr pass def setattr(x,y,v):#setattr pass def delattr(x,y): #delattr pass
四个方法的使用演示
class Foo: f = '类的静态变量' def __init__(self,name,age): self.name = name self.age = age def say_hi(self): print('hi,%s'%self.name) obj = Foo('egon',73) #检测是否含有某种属性 print(hasattr(obj,'name')) print(hasattr(obj,'say_hi')) #结果:True True #获取属性 n = getattr(obj,'name') print(n) #结果:egon func = getattr(obj,'say_hi') func() #结果:hi,egon #print(getattr(obj,'aaaaaaa')) #报错 #设置属性 setattr(obj,'sb',True) setattr(obj,'show_name',lambda self:self.name+'sb') print(obj.__dict__) print(obj.show_name(obj)) #结果:{'name': 'egon', 'age': 73, 'sb': True, 'show_name': <function <lambda> at 0x05E56300>} #egonsb #删除属性 delattr(obj,'age') delattr(obj,'show_name') #delattr(obj,'show_name111')#不存在,会报错 print(obj.__dict__) #结果:{'name': 'egon', 'sb': True}
class Foo(object): staticField = 'old boy' def __init__(self): self.name = 'wupeiqi' def func(self): print('abcd') @staticmethod def bar(): return 'bar' foo = Foo() print(getattr(Foo, 'staticField')) print(getattr(Foo, 'func')) print(getattr(Foo, 'bar')) print(getattr(foo,'staticField')) print(getattr(foo, 'func')) func = getattr(foo,'func') func() #或者是 getattr(foo,'func')() print(getattr(foo, 'bar'))
old boy <function Foo.func at 0x04C2B1E0> <function Foo.bar at 0x04C2B078> old boy <bound method Foo.func of <__main__.Foo object at 0x04C3A530>> abcd abcd <function Foo.bar at 0x04C2B078>
import sys def s1(): print('s1') def s2(): print('s2') this_module = sys.modules[__name__] hasattr(this_module, 's1') getattr(this_module, 's2') func = getattr(this_module, 's2') func() #或者是 getattr(this_module,'s2')() #结果:True #<function __main__.s2()> #s2 #s2
导入其他模块,利用反射查找该模块是否存在某个方法
def test(): print('from the test') #假设该模块单元为module_test import module_test as obj print(hasattr(obj,'test')) getattr(obj,'test')()
__str__和__repr__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
format_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名 } class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __repr__(self): return 'School(%s,%s)' %(self.name,self.addr) def __str__(self): return '(%s,%s)' %(self.name,self.addr) def __format__(self, format_spec): # if format_spec if not format_spec or format_spec not in format_dict: format_spec='nat' fmt=format_dict[format_spec] return fmt.format(obj=self) s1=School('oldboy1','北京','私立') print('from repr: ',repr(s1)) print('from str: ',str(s1)) print(s1) ''' str函数或者print函数--->obj.__str__() repr或者交互式解释器--->obj.__repr__() 如果__str__没有被定义,那么就会使用__repr__来代替输出 注意:这俩方法的返回值必须是字符串,否则抛出异常 ''' print(format(s1,'nat')) print(format(s1,'tna')) print(format(s1,'tan')) print(format(s1,'asfdasdffd'))
from repr: School(oldboy1,北京) from str: (oldboy1,北京) (oldboy1,北京) oldboy1-北京-私立 私立:oldboy1:北京 私立/北京/oldboy1 oldboy1-北京-私立
class B: def __str__(self): return 'str:class B' def __repr__(self): return 'repr:class B' b = B() print('%s'%b) print('%r'%b) #%r打印时能够重现它所代表的对象 text = 'I am 22 years old' print('I said: %s'%text) print('I said: %r'%text)
item系列?????
__getitem__\__setitem__\__delitem__
#类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__里的 #对象的__dict__中存储了一些self.xxx的一些东西 class Foo: def __init__(self,name): self.name = name def __getitem__(self,item): print(self.__dict__[item]) def __setitem__(self,key,value): self.__dict__[key] = value def __delitem__(self,key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self,item): print('del obj.key时,我执行') self.__dict__.pop(item) f1 = Foo('sb') f1['age'] = 18 f1['age1'] = 19 del f1.age1 del f1['age'] f1['name'] = 'alex' print(f1.__dict__)
del obj.key时,我执行 del obj[key]时,我执行 {'name': 'alex'}
__del__
析构方法,当对象在内存中被释放时,自动触发执行
注:此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行的,所以,
析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
class Foo: def __del__(self): print('执行我了') f1 = Foo() del f1 print('------------------>')
__new__
class A: def __init__(self): self.x = 1 print('in init function') def __new__(cls,*arg,**kwargs): print('in new function') return object.__new__(A) a = A() print(a.x)
in new function in init function 1
class Singleton: def __new__(cls,*args,**kw): if not hasattr(cls,'_instance'): cls._instance = object.__new__(cls) return cls._instance one = Singleton() two = Singleton() two.a = 3 print(one.a) #结果:3 #one和two完全相同,可以用id(),==,is检测 print(id(one))#76196720 print(id(two))#76196720 print(one == two)#True print(one is two)#True
__call__
对象后面加括号,触发执行
注:构造方法的执行是由创建对象触发的,即:对象=类名();而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()()
class Foo: def __init__(self): pass def __call__(self,*args,**kwargs): print('__call__') obj = Foo() #执行__init__ obj() #结果:__call__
with和__enter__,__exit__
import pickle class MyPickledump: def __init__(self,path): self.path = path def __enter__(self): self.f = open(self.path, mode='ab') return self def dump(self,content): pickle.dump(content,self.f) def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() class Mypickleload: def __init__(self,path): self.path = path def __enter__(self): self.f = open(self.path, mode='rb') return self def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() def __iter__(self): while True: try: yield pickle.load(self.f) except EOFError: break # with MyPickledump('file') as f: # f.dump({1,2,3,4}) with Mypickleload('file') as f: for item in f: print(item)
__len__
class A: def __init__(self): self.a = 1 self.b = 2 def __len__(self): return len(self.__dict__) a = A() print(len(a)) #结果:2
__hash__
class A: def __init__(self): self.a = 1 self.b = 2 def __hash__(self): return hash(str(self.a)+str(self.b)) #相当于hash('12') a = A() print(hash(a)) #结果:1122401819
__eq__
class A: def __init__(self): self.a = 1 self.b = 2 def __eq__(self,obj): if self.a == obj.a and self.b == obj.b: return True a = A() b = A() print(a == b)
使用__slots__
正常情况下,当我们定义了一个class,创建了一个class实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:
class Student(): pass
然后,尝试给实例绑定一个属性:
s = Student() s.name = 'Michael' #给实例绑定一个属性 print(s.name)
还可以尝试给实例绑定一个方法:
def set_age(self,age): self.age = age from types import MethodType s.set_age = MethodType(set_age,s) #将set_age方法绑定到对象上,第一个参数是绑定的方法,第二个参数是要绑定的对象,第三个参数是类名(可省略) s.set_age(25) #调用实例方法 s.age
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:
S2 = Student() #创建新的实例 s2.set_age(25) #尝试调用方法
Error:name 's2' is not defined
为了给所有实例都绑定方法,可以给class绑定方法:
def set_score(self,score): #只是定义了一个方法 self.score = score Student.set_score = set_score #把这个方法赋值给类方法作为类方法,关键步骤
给class绑定方法后,所有实例均可调用:
s = Student() s.set_score(100) s.score s2 = Student() s2.set_score(99) s2.score
100 99
使用__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(): __slots__ = ('name','age') #用tuple定义允许绑定的属性名称 s = Student() s.name = 'Michael' s.age = 25 s.score = 25 #运行此条语句函数会报错
由于‘score’没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
class GraduateStudent(Student): __slots__ = ('score','price') g = GraduateStudent() g.name = 'ninini' g.score = 15
使用@property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
class Student(): pass a = Student() a.score = 99
这显然不合逻辑,为了限制score范围,可以通过set_score函数来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(): def get_score(self): return self._score def set_score(self,value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0~100!') self._score = value
现在,对任意的Student实例进行操作,就不能随心所欲地设置score了
s = Student() s.set_score(60) s.get_score()
60
s.set_score(9999)
ValueError: score must between 0~100!
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的python,这是必须要做到的!
装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器一样起作用。python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(): @property def score(self): return self._score @score.setter def score(self,value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0~100!') self._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter()方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器
@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作
s = Student() s.score = 60 #实际转化为s.set_score(60) s.score #实际转化为s.get_score()
60
s.score = 999
score must between 0~100!
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
还可以定义只读属性,只定义getter方法,不定义setter就是一个只读属性:
class Student(): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015-self._birth
上面的birth是可读写属性,而age是只读属性,age可以根据birth和当前时间来算出来
小结
@property广泛应用在类的定义中,可以让调用者写出简洁的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
如果按照一个一个的顺序,那么类的数量会按照指数增长,很明显这样设计是不行的
正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:
class Animal(): pass class Mammal(Animal): pass class Bird(Animal): pass class Dog(Mammal): pass class Bat(Mammal): pass class Parrot(Bird): pass class Ostrich(Bird): pass
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类
class Runnable(): def run(self): print('Running') class Flyable(): def fly(self): print('Flying')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal,Runnable): pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mammal, Flyable): pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
Mixln
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,
让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixln。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物
HerbivoresMixIn,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn): pass
Mixln的目的就是给一个类增加多个功能,这样在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
python自带的很多库也使用了MixIn。举个例子,python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,
这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn): pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn): pass
如果你打算搞一个更先进的协程模式,可以编写一个CoroutineMixIn:
class MyTCPserver(TCPServer,CoroutineMixIn): pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
小结
由于python允许使用多重继承,因此,MixIn就是一种常见的设计。
只允许但一继承的语言(如java)不能使用MixIn的设计。
定制类
看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在python中是有特殊用途的。
__slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()函数。
除此之外,python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
__str__
我们先定义一个Student类,打印一个实例:
class Student(): def __init__(self, name): self.name = name print(Student('Michael'))
<__main__.Student object at 0x04B196B0>
打印出一堆<__main__.Student object at 0x04B196B0>,不好看。
怎么才能打印的好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:
class Student(): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name: %s)' %self.name print(Student('Michael'))
Student object (name: Michael)
这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
但是细心的朋友会发现敲变量不用print,打印出来的实例还是不好看:
class Student(): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name: %s)' %self.name s = Student('adb') s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用于看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,
__repr__()是为了调试服务的。
解决方法是在定义一个__repr__(),但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__ s = Student('ba') s
na
__iter__
如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__方法,该方法返回一个迭代对象,然后,python的for循环就会不断调用该迭代对象的
__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(): def __init__(self): self.a, self.b = 0,1 #初始化两个计数器a,b def __iter__(self): return self #实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a +self.b if self.a > 100000: #退出循环的条件 raise StopIteration() return self.a #返回下一个值
现在,把Fib实例作用于for循环:
1 1 2 3 5 8 13 ...
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第五个元素:
要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:
class Fib(): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a+b return a f = Fib() f[0] f[1] f[2]
1 1 2
但是list有个神奇的切片方法:
list(range(100))[5:10]
对于切片Fib却报错,原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象,所以要做判断:
class Fib(): def __getitem__(self,n): if isinstance(n, int): #n是索引 a, b = 1, 1 for x in range(n): a, b = b ,a+b return n if isinstance(n,slice): #n是切片 start = n.start stop = n.stop if start is None: start = 0 a,b = 1,1 L = [] for x in range(stop): if x >=start: L.append(a) a, b = b, a+b return L
结果如下,但是并未对step参数进行处理:
f = Fib() f[0:5] f[0:5,2] #不可以
总之,通过上面的方法我们自己定义的类表现得和python自带的list、tuple、dict没什么区别,这完全归功于动态语言的’鸭子类型‘,不需要强制继承某个接口。
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义student类:
class Student(): def __init__(self): self.name = 'Michael' s = Student() print(s.name) print(s.score) #AttributeError: 'Student' object has no attribute 'score':没有找到score这个attribute
要避免这个错误,除了可以再加上一个score属性外,python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性,修改如下:
class Student(): def __inti__(self): self.name = 'Michael' def __getattr__(self, attr): if attr == 'score': return 99
当调用不存在的属性时,比如score,python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
s = Student() s.name s.score #99
返回函数也是完全可以的:
class Student(): def __getattr__(self,attr): if attr == 'age': return lambda:25 s = Student() s.age()
25
注意:只有在没有找到属性的情况下,才调用__getattr__,已有属性,比如name,不会再__getarrt__中查找。
此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None,要让class只响应特定的几个属性,我们就要按照约定,抛出
AttributeError的错误:
class Student(): def __getattr__(self,attr): if attr == 'age': return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr) s = Student() s.score
AttributeError: 'Student' object has no attribute 'score'
这实际上可以把一个类所有属性和方法调用全部动态化处理了,不需要任何特殊手段
这种完全动态调用的作用就是,可以针对完全动态的情况作调用。
举个例子:
现在很多网站都搞REST API,比如新浪微博,豆瓣,调用API的URL类似:
- http://api.server/user/friends
- http://api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__,我们可以写出一个链式调用:
class Chain(): def __init__(self, path=''): self.__path = path def __getattr__(self,path): return Chain("%s%s"%(self.path, path)) def __str__(self): return self.path __repr__ = __str__
后面的有些看不懂,以后再写吧
————————————————————————————————python的错误处理--try语句————————————————————————————————
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句快,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
python的try语句有两种风格
1.处理异常(try/expect/else)
2.无论是否发生异常都将执行最后的代码(try/finally)
常见异常AttributeError 试图访问一个对象,class MyObject(): def __init__(self): self.x = 9 def power(self): return self.x * self.xobj = MyObject()hasattr(obj,'x') # 有属性‘x’吗setattr(obj,'y',19) # 设置一个属性‘y’getattr(obj,'y') # 获取属性‘y’
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入
/
输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[
5
]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl
+
C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
处理单个异常
name = [1,2,3] try: name[3] #不存在3这个下标值 except IndexError as e: #抓取indexError 这个异常 print(e) #e 是错误详情
list index out of range
处理多个异常
name = [1,2,3] data = {'a':'b'} try: data['c'] #这边已经出现了异常keyError,所以直接跳出code,先到第一个except,不是,然后跳到keyError下去处理 name[3] except IndexError as e: print(e) except KeyError as e: print(e)
'c'
处理多个异常写成一个except:
try: data['c'] name[3] except(IndexError,KeyError) as e: print(e)
'c'
try: open("qigao.text","r",encoding="utf-8") except (IndexError,KeyError) as e: #没有IndexError,KeyError这两个异常 print(e) except Exception as e: #只能通过这个异常处理,Exception 抓住所有的异常 print(e)
[Errno 2] No such file or directory: 'qigao.text'
else作用
作用:没有异常,则走else部分的逻辑代码
try: print('qigao,handson') #代码没有异常 except(IndexError,KeyError) as e: print(e) except(IndexError,KeyError) as e: print(e) else: print('没有异常')
qigao,handson 没有异常
finally作用
不管有没有错误,都会执行finally作用
①无异常情况
try: print('qigao,handson') #没有异常 except(IndexError,KeyError) as e: print(e) except Exception as e: print(e) else: print('没有异常') finally: print('不管有没有错,都有这个')
qigao,handson 没有异常 不管有没有错,都有这个
②有异常情况
try: data = {'a':'b'} data['c'] #字典中没有'c'这个key值 except(IndexError,KeyError) as e: print(e) except Exception as e: print(e) else: print('没有异常') finally: print('不管有没有错,都有这个')
'c' 不管有没有错,都有这个
自定义异常
class Student(): def get_score(self): return self._score def set_score(self,value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0~100!') self._score = value
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------