一、面向对象概述
如今主流的软件开发思想有两种:一个是面向过程,另一个是面向对象。面向过程出现得较早,典型代表为C语言,开发中小型项目的效率很高,但是很难适用于如今主流的大中型项目开发场景。面向对象则出现得更晚一些,典型代表为Java或C++等语言,更加适合用于大型开发场景。两种开发思想各有长短。
对于面向过程的思想: 需要实现一个功能的时候,看重的是开发的步骤和过程,每一个步骤都需要自己亲力亲为,需要自己编写代码(自己来做)
对于面向对象的思想:当需要实现一个功能的时候,看重的并不是过程和步骤,而是关心谁帮我做这件事(偷懒,找人帮我做)
面向对象的三大特征有:封装性、继承性、多态性。
1.1 面向过程与面向对象
二、类与对象
面向对象编程的2个非常重要的概念:类和对象。
我们学习编程语言,就是为了模拟现实世界中的事物,实现信息化来提高工作效率。例如银行的业务系统、超市的结账系统等,都是如此。
面向对象的语言当中,“类”就是用来模拟现实事物的。
那么模拟现实世界的事物通常从两方面模拟:
- 属性:事物的特征描述信息,用于描述某个特征“是什么”。
- 行为:事物的能力行动方案,用于说明事物“能做什么”。
类中也有属性、行为两个组成部分,而“对象”是类的具体实例。例如:
- 类:抽象的,是一张“手机设计图”。
- 对象:具体的,是一个“真正的手机实例”。
2.1 初识类
python中一切皆为对象,类型的本质就是类。
>>> dict # 类型dict就是类dict <class 'dict'> >>> d=dict(name='eva') #实例化 >>> d.pop('name') # 向d发一条消息,执行d的方法pop 'eva'
2.2 类的定义
定义一个类,格式如下:
class 类名: 方法列表
定义一个People类:
1 # 经典类 python2中才有的 2 # class People: 3 # pass 4 5 6 # 新式类 python3中默认都是新式类 7 class People(object): 8 9 def walk(self): 10 print("walk....")
说明:
- 定义类的形式有两种,新式类和经典类,python3中默认都是新式类,python2中有新式类和经典类两种
- object类是python中所有类的最顶级父类
- 类名的命名方式按照"大驼峰命名法"
- walk 是一个实例方法,第一个参数一般是self,表示实例对象本身,当然了可以将self换为其它的名字,其作用是一个变量 这个变量指向了实例对象
2.2.1 对象的创建
python中,可以根据已经定义的类去创建出一个或多个对象。
创建对象的格式为:
对象名1 = 类名() 对象名2 = 类名() 对象名3 = 类名()
1 class Hero(object): # 新式类定义形式 2 """info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self""" 3 def info(self): 4 """当对象调用实例方法时,Python会自动将对象本身的引用做为参数, 5 传递到实例方法的第一个参数self里""" 6 print(self) 7 print("self各不同,对象是出处。") 8 9 10 # Hero这个类 实例化了一个对象 taidamier(泰达米尔) 11 h = Hero() 12 13 # 对象调用实例方法info(),执行info()里的代码 14 # . 表示选择属性或者方法 15 h.info() 16 17 print(h) # 打印对象,则默认打印对象在内存的地址,结果等同于info里的print(self) 18 print(id(h)) # id(h) 则是内存地址的十进制形式表示
2.2.2 __init__实例方法
实例化:类名加括号就是实例化(创建对象),会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
1 class Hero(object): 2 """定义了一个英雄类,可以移动和攻击""" 3 # Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。 4 # 如果类面没有写__init__方法,Python会自动创建,但是不执行任何操作, 5 # 如果为了能够在完成自己想要的功能,可以自己定义__init__方法, 6 # 所以一个类里无论自己是否编写__init__方法 一定有__init__方法。 7 8 def __init__(self): 9 """ 方法,用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用""" 10 self.name = "aa" # 姓名 11 self.hp = 2600 # 生命值 12 self.atk = 450 # 攻击力 13 self.armor = 200 # 护甲值 14 15 def move(self): 16 """实例方法""" 17 print("正在前往事发地点...") 18 19 def attack(self): 20 """实例方法""" 21 print("发出了一招强力的普通攻击...") 22 23 24 hero = Hero() 25 print(hero.name) 26 hero.move()
说明:
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用- __init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。
带参数的__init__方法:
1 class Person(object): 2 3 def __init__(self, name, age): 4 self.name = name # 实例属性 5 self.age = age 6 7 def play(self): 8 print("愉快的玩耍") 9 10 def info(self): 11 print("姓名:", self.name) # 在方法内使用self获取对象属性 12 print("年龄:", self.age) 13 14 15 # 实例化对象,参数传递到__init__方法里 16 p1 = Person("aa", 12) 17 p1.info()
说明:
- 通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样
__init__(self)
中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__(self)
中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
- 在类内部获取 属性 和 实例方法,通过self获取
- 在类外部获取 属性 和 实例方法,通过对象名获取
- 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址
- 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。
2.2.3 类的属性
1 class Person: # 定义一个人类 2 role = 'person' # 人的角色属性都是人 3 4 def walk(self): #人都可以走路,也就是有一个走路方法 5 print("person is walking...") 6 7 8 print(Person.role) # 查看人的role属性 9 print(Person.walk) # 引用人的走路方法,注意,这里不是在调用 10 11 p = Person() 12 p.role = "aa" 13 print(p.role) # 实例属性会屏蔽掉同名的类属性 aa 14 print(Person.role) # person
如果需要在类外修改类属性
,必须通过类对象
去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性
,这种方式修改的是实例属性
,不会影响到类属性
,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性
,除非删除了该实例属性
。
2.2.4 类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
2.2.5 对象之间的交互
1 # egon大战哈士奇 2 class Person: # 定义一个人类 3 role = 'person' # 人的角色属性都是人 4 5 def __init__(self, name, aggressivity, life_value): 6 self.name = name # 每一个角色都有自己的昵称; 7 self.aggressivity = aggressivity # 每一个角色都有自己的攻击力; 8 self.life_value = life_value # 每一个角色都有自己的生命值; 9 10 def attack(self,dog): 11 # 人可以攻击狗,这里的狗也是一个对象。 12 # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降 13 dog.life_value -= self.aggressivity 14 15 16 class Dog: # 定义一个狗类 17 role = 'dog' # 狗的角色属性都是狗 18 19 def __init__(self, name, breed, aggressivity, life_value): 20 self.name = name # 每一只狗都有自己的昵称; 21 self.breed = breed # 每一只狗都有自己的品种; 22 self.aggressivity = aggressivity # 每一只狗都有自己的攻击力; 23 self.life_value = life_value # 每一只狗都有自己的生命值; 24 25 def bite(self,people): 26 # 狗可以咬人,这里的狗也是一个对象。 27 # 狗咬人,那么人的生命值就会根据狗的攻击力而下降 28 people.life_value -= self.aggressivity 29 30 31 egg = Person('egon',10,1000) #创造了一个实实在在的人egg 32 ha2 = Dog('二愣子','哈士奇',10,1000) #创造了一只实实在在的狗ha2 33 print(ha2.life_value) #看看ha2的生命值 34 egg.attack(ha2) #egg打了ha2一下 35 print(ha2.life_value) #ha2掉了10点血
1 # 一个简单的例子帮你理解面向对象 2 from math import pi 3 4 5 class Circle: 6 ''' 7 定义了一个圆形类; 8 提供计算面积(area)和周长(perimeter)的方法 9 ''' 10 def __init__(self,radius): 11 self.radius = radius 12 13 def area(self): 14 return pi * self.radius * self.radius 15 16 def perimeter(self): 17 return 2 * pi *self.radius 18 19 20 circle = Circle(10) # 实例化一个圆 21 area1 = circle.area() # 计算圆面积 22 per1 = circle.perimeter() # 计算圆周长 23 print(area1, per1) # 打印圆面积和周长
2.3 类命名空间与对象、实例的命名空间
-
静态属性就是直接在类中定义的变量
-
动态属性就是定义在类中的方法
1 class Person(object): 2 3 role = "person" 4 5 def __init__(self): 6 pass 7 8 def walk(self): 9 pass 10 11 12 p1 = Person() 13 p2 = Person() 14 15 print(id(Person.role)) 16 print(id(p1.role)) 17 print(id(p2.role)) 18 19 print(Person.walk) 20 print(p1.walk) 21 print(p2.walk) 22 23 # 运行结果: 24 # 2427822463496 25 # 2427822463496 26 # 2427822463496 27 #28 # > 29 #>
说明:
- 类的数据数据是共享给所有对象使用的
- 类的动态属性是绑定到所有对象的
- 创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
- 如:obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
2.4 面向对象的组合用法
1 """ 2 圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。 3 这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用 4 """ 5 6 from math import pi 7 8 9 class Circle: 10 ''' 11 定义了一个圆形类; 12 提供计算面积(area)和周长(perimeter)的方法 13 ''' 14 def __init__(self,radius): 15 self.radius = radius 16 17 def area(self): 18 return pi * self.radius * self.radius 19 20 def perimeter(self): 21 return 2 * pi *self.radius 22 23 24 circle = Circle(10) # 实例化一个圆 25 area1 = circle.area() # 计算圆面积 26 per1 = circle.perimeter() # 计算圆周长 27 print(area1, per1) # 打印圆面积和周长 28 29 30 class Ring: 31 ''' 32 定义了一个圆环类 33 提供圆环的面积和周长的方法 34 ''' 35 def __init__(self,radius_outside,radius_inside): 36 self.outsid_circle = Circle(radius_outside) 37 self.inside_circle = Circle(radius_inside) 38 39 def area(self): 40 return self.outsid_circle.area() - self.inside_circle.area() 41 42 def perimeter(self): 43 return self.outsid_circle.perimeter() + self.inside_circle.perimeter() 44 45 46 ring = Ring(10, 5) # 实例化一个环形 47 print(ring.perimeter()) # 计算环形的周长 48 print(ring.area()) # 计算环形的面积
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
1 class BirthDate: 2 def __init__(self,year,month,day): 3 self.year=year 4 self.month=month 5 self.day=day 6 7 8 class Couse: 9 def __init__(self,name,price,period): 10 self.name=name 11 self.price=price 12 self.period=period 13 14 15 class Teacher: 16 def __init__(self,name,gender,birth,course): 17 self.name=name 18 self.gender=gender 19 self.birth=birth 20 self.course=course 21 22 def teach(self): 23 print('teaching') 24 25 26 p1=Teacher('egon','male', 27 BirthDate('1995','1','27'), 28 Couse('python','28000','4 months') 29 ) 30 31 32 print(p1.birth.year,p1.birth.month,p1.birth.day) 33 34 35 print(p1.course.name,p1.course.price,p1.course.period) 36 ''' 37 运行结果: 38 27 39 python 28000 4 months 40 '''
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好.
三、面向对象的三大特性
3.1 继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。
Python的继承分为单继承和多继承:
class ParentClass1(object): # 定义父类 pass class ParentClass2(object): # 定义父类 pass class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): # python支持多继承,用逗号分隔开多个继承的类 pass
查看继承:
>>> SubClass1.__bases__ # __bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
ps:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
3.1.1 单继承
1 # 定义一个Master类 2 class Master(object): 3 def __init__(self): 4 # 属性 5 self.kongfu = "古法煎饼果子配方" 6 7 # 实例方法 8 def make_cake(self): 9 print("按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 10 11 12 # 定义Prentice类,继承了 Master,则Prentice是子类,Master是父类。 13 class Prentice(Master): 14 # 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。 15 pass 16 17 # laoli = Master() 18 # print(laoli.kongfu) 19 # laoli.make_cake() 20 21 22 damao = Prentice() # 创建子类实例对象 23 print(damao.kongfu) # 子类对象可以直接使用父类的属性 24 damao.make_cake() # 子类对象可以直接使用父类的方法
注:虽然子类没有定义__init__
方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__
方法
3.1.2 多继承
1 class Master(object): 2 def __init__(self): 3 self.kongfu = "古法煎饼果子配方" # 实例变量,属性 4 5 def make_cake(self): # 实例方法,方法 6 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 7 8 def dayandai(self): 9 print("师傅的大烟袋..") 10 11 12 class School(object): 13 def __init__(self): 14 self.kongfu = "现代煎饼果子配方" 15 16 def make_cake(self): 17 print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 18 19 def xiaoyandai(self): 20 print("学校的小烟袋..") 21 22 23 # class Prentice(School, Master): # 多继承,继承了多个父类(School在前) 24 # pass 25 26 # damao = Prentice() 27 # print(damao.kongfu) 28 # damao.make_cake() 29 # damao.dayandai() 30 # damao.xiaoyandai() 31 32 33 class Prentice(Master, School): # 多继承,继承了多个父类(Master在前) 34 pass 35 36 37 damao = Prentice() 38 print(damao.kongfu) # 执行Master的属性 39 damao.make_cake() # 执行Master的实例方法 40 41 # 子类的魔法属性__mro__决定了属性和方法的查找顺序 42 print(Prentice.__mro__) 43 44 damao.dayandai() # 不重名不受影响 45 damao.xiaoyandai()
说明:
- 多继承可以继承多个父类,也继承了所有父类的属性和方法
- 注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找)
- 多个父类中,不重名的属性和方法,不会有任何影响。
Python2中多继承中的问题:
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): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
Python的继承原理:对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
3.1.3 派生
3.1.3.1子类重写父类的同名属性和方法
1 class Master(object):
2
3 def __init__(self):
4 self.kongfu = "古法煎饼果子配方"
5
6 def make_cake(self):
7 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
8
9
10 class School(object):
11 def __init__(self): 12 self.kongfu = "现代煎饼果子配方" 13 14 def make_cake(self): 15 print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 16 17 18 class Prentice(School, Master): # 多继承,继承了多个父类 19 20 def __init__(self): 21 self.kongfu = "猫氏煎饼果子配方" 22 23 def make_cake(self): 24 print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 25 26 27 # 如果子类和父类的方法名和属性名相同,则默认使用子类的 28 # 叫 子类重写父类的同名方法和属性 29 damao = Prentice() 30 print(damao.kongfu) # 子类和父类有同名属性,则默认使用子类的 31 damao.make_cake() # 子类和父类有同名方法,则默认使用子类的 32 33 # 子类的魔法属性__mro__决定了属性和方法的查找顺序 34 print(Prentice.__mro__)
3.1.3.2 子类调用父类的同名方法和属性
1 class Master(object): 2 def __init__(self): 3 self.kongfu = "古法煎饼果子配方" # 实例变量,属性 4 5 def make_cake(self): # 实例方法,方法 6 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 7 8 9 class School(object): 10 def __init__(self): 11 self.kongfu = "现代煎饼果子配方" 12 13 def make_cake(self): 14 print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 15 16 17 class Prentice(School, Master): # 多继承,继承了多个父类 18 19 def __init__(self): 20 self.kongfu = "猫氏煎饼果子配方" 21 22 def make_cake(self): 23 print("执行子类的__init__方法前,self.kongfu属性:%s" % self.kongfu) 24 self.__init__() # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...." 25 print("执行子类的__init__方法前,self.kongfu属性:%s" % self.kongfu) 26 print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 27 28 # 调用父类方法格式:父类类名.父类方法(self) 29 def make_old_cake(self): 30 # 不推荐这样访问父类的实例属性,相当于创建了一个新的父类对象 31 # print("直接调用Master类的kongfu属性:%s" % Master().kongfu) 32 33 # 可以通过执行Master类的__init__方法,来修改self的属性值 34 print("执行Master类的__init__方法前,self.kongfu属性:%s" % self.kongfu) 35 Master.__init__(self) # 调用了父类Master的__init__方法 self.kongfu = "古法...." 36 print("执行Master类的__init__方法后,self.kongfu属性:%s" % self.kongfu) 37 Master.make_cake(self) # 调用父类Master的实例方法 38 39 def make_new_cake(self): 40 # 不推荐这样访问类的实例属性,相当于创建了一个新的父类对象 41 # print("直接调用School类的kongfu属性:%s" % School().kongfu) 42 43 # 可以通过执行School类的__init__方法,来修改self的属性值 44 print("执行School类的__init__方法前,self.kongfu属性:%s" % self.kongfu) 45 School.__init__(self) # 调用了父类School的__init__方法 self.kongfu = "现代...." 46 print("执行School类的__init__方法后,self.kongfu属性:%s" % self.kongfu) 47 School.make_cake(self) # 调用父类School的实例方法 48 49 50 # 实例化对象,自动执行子类的__init__方法 51 damao = Prentice() 52 53 damao.make_cake() # 调用子类的方法(默认重写了父类的同名方法) 54 55 print("--" * 10) 56 damao.make_old_cake() # 进入实例方法去调用父类Master的方法 57 58 print("--" * 10) 59 damao.make_new_cake() # 进入实例方法去调用父类School的方法 60 61 print("--" * 10) 62 damao.make_cake() # 调用本类的实例方法
3.1.3.3 多层继承
1 class Master(object): 2 def __init__(self): 3 self.kongfu = "古法煎饼果子配方" 4 5 def make_cake(self): 6 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 7 8 9 class School(object): 10 def __init__(self): 11 self.kongfu = "现代煎饼果子配方" 12 13 def make_cake(self): 14 print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 15 16 17 class Prentice(School, Master): # 多继承,继承了多个父类 18 19 def __init__(self): 20 self.kongfu = "猫氏煎饼果子配方" 21 self.money = 10000 # 亿美金 22 23 def make_cake(self): 24 self.__init__() # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...." 25 print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 26 27 # 调用父类方法格式:父类类名.父类方法(self) 28 def make_old_cake(self): 29 Master.__init__(self) # 调用了父类Master的__init__方法 self.kongfu = "古法...." 30 Master.make_cake(self) # 调用了父类Master的实例方法 31 32 def make_new_cake(self): 33 School.__init__(self) # 调用了父类School的__init__方法 self.kongfu = "现代...." 34 School.make_cake(self) # 调用父类School的实例方法, 35 36 37 class PrenticePrentice(Prentice): # 多层继承 38 pass 39 40 41 pp = PrenticePrentice() 42 pp.make_cake() # 调用父类的实例方法 43 pp.make_new_cake() 44 pp.make_old_cake() 45 46 print(pp.money)
3.1.3.4 super的使用
1 class Master(object): 2 def __init__(self): 3 self.kongfu = "古法煎饼果子配方" # 实例变量,属性 4 5 def make_cake(self): # 实例方法,方法 6 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 7 8 9 # 父类是 Master类 10 class School(Master): 11 12 def __init__(self): 13 self.kongfu = "现代煎饼果子配方" 14 15 def make_cake(self): 16 print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 17 super().__init__() # 执行父类的构造方法 18 super().make_cake() # 执行父类的实例方法 19 20 21 # 父类是 School 和 Master 22 class Prentice(School, Master): # 多继承,继承了多个父类 23 24 def __init__(self): 25 self.kongfu = "猫氏煎饼果子配方" 26 27 def make_cake(self): 28 self.__init__() # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...." 29 print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 30 31 def make_all_cake(self): 32 # 方式1. 指定执行父类的方法(代码臃肿) 33 # School.__init__(self) 34 # School.make_cake(self) 35 # 36 # Master.__init__(self) 37 # Master.make_cake(self) 38 # 39 # self.__init__() 40 # self.make_cake() 41 42 # 方法2. super() 带参数版本,只支持新式类 43 # super(Prentice, self).__init__() # 执行父类的 __init__方法 44 # super(Prentice, self).make_cake() 45 # self.make_cake() 46 47 # 方法3. super()的简化版,只支持新式类 48 super().__init__() # 执行父类的 __init__方法 49 super().make_cake() # 执行父类的 实例方法 50 self.make_cake() # 执行本类的实例方法 51 52 53 damao = Prentice() 54 damao.make_cake() 55 damao.make_all_cake() 56 57 # print(Prentice.__mro__)
注意:如果继承了多个父类,且父类都有同名方法,则默认只执行第一个父类的(同名方法只执行一次,目前super()不支持执行多个父类的同名方法),super时python2.3版本以上才出现的。
3.1.4 抽象类
1 # 一切皆文件 2 import abc # 利用abc模块实现抽象类 3 4 5 class All_file(metaclass=abc.ABCMeta): 6 all_type = 'file' 7 8 @abc.abstractmethod # 定义抽象方法,无需实现功能 9 def read(self): 10 '子类必须定义读功能' 11 pass 12 13 @abc.abstractmethod # 定义抽象方法,无需实现功能 14 def write(self): 15 '子类必须定义写功能' 16 pass 17 18 19 # class Txt(All_file): 20 # pass 21 # 22 # t1=Txt() # 报错,子类没有定义抽象方法 23 24 25 class Txt(All_file): # 子类继承抽象类,但是必须定义read和write方法 26 def read(self): 27 print('文本数据的读取方法') 28 29 def write(self): 30 print('文本数据的读取方法') 31 32 33 class Sata(All_file): # 子类继承抽象类,但是必须定义read和write方法 34 def read(self): 35 print('硬盘数据的读取方法') 36 37 def write(self): 38 print('硬盘数据的读取方法') 39 40 41 class Process(All_file): # 子类继承抽象类,但是必须定义read和write方法 42 def read(self): 43 print('进程数据的读取方法') 44 45 def write(self): 46 print('进程数据的读取方法') 47 48 49 wenbenwenjian=Txt() 50 51 52 yingpanwenjian=Sata() 53 54 55 jinchengwenjian=Process() 56 57 58 # 这样大家都是被归一化了,也就是一切皆文件的思想 59 wenbenwenjian.read() 60 yingpanwenjian.write() 61 jinchengwenjian.read() 62 63 64 print(wenbenwenjian.all_type) 65 print(yingpanwenjian.all_type) 66 print(jinchengwenjian.all_type)
3.2 封装
封装的意义:
- 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;
- 对类的属性和方法增加 访问权限控制。
私有权限:在属性名和方法名 前面 加上两个下划线 __
- 类的私有属性 和 私有方法,都不能通过对象直接访问,但是可以在本类内部访问;
- 类的私有属性 和 私有方法,都不会被子类继承,子类也无法访问;
- 私有属性 和 私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用。
3.2.1 私有变量
1 # 其实这仅仅这是一种变形操作 2 # 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: 3 4 5 class A: 6 __N=0 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N 7 def __init__(self): 8 self.__X=10 # 变形为self._A__X 9 10 def __foo(self): # 变形为_A__foo 11 print('from A') 12 13 def bar(self): 14 self.__foo() # 只有在类内部才可以通过__foo的形式访问到. 15 16 17 # A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形。
3.2.2 私有方法
1 class Master(object): 2 def __init__(self): 3 self.kongfu = "古法煎饼果子配方" 4 def make_cake(self): 5 print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 6 7 class Prentice(School, Master): 8 def __init__(self): 9 self.kongfu = "猫氏煎饼果子配方" 10 # 私有属性,可以在类内部通过self调用,但不能通过对象访问 11 self.__money = 10000 12 13 # 私有方法,可以在类内部通过self调用,但不能通过对象访问 14 def __print_info(self): 15 print(self.kongfu) 16 print(self.__money) 17 18 def make_cake(self): 19 self.__init__() 20 print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu) 21 22 def make_old_cake(self): 23 Master.__init__(self) 24 Master.make_cake(self) 25 26 27 def make_new_cake(self): 28 School.__init__(self) 29 School.make_cake(self) 30 31 class PrenticePrentice(Prentice): 32 pass 33 34 35 damao = Prentice() 36 # 对象不能访问私有权限的属性和方法 37 # print(damao.__money) 38 # damao.__print_info() 39 40 41 pp = PrenticePrentice() 42 # 子类不能继承父类私有权限的属性和方法 43 print(pp.__money) 44 pp.__print_info()
3.2.3 封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
1 # 类的设计者 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 10 def tell_area(self): # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 11 return self.__width * self.__length 12 13 14 #使用者 15 >>> r1=Room('卧室','egon',20,20,20) 16 >>> r1.tell_area() #使用者调用接口tell_area 17 18 19 20 21 #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 22 class Room: 23 def __init__(self,name,owner,width,length,high): 24 self.name=name 25 self.owner=owner 26 self.__width=width 27 self.__length=length 28 self.__high=high 29 30 def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 31 return self.__width * self.__length * self.__high 32 33 34 #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 35 >>> r1.tell_area()
3.2.4 property属性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
1 class People: 2 def __init__(self,name,weight,height): 3 self.name=name 4 self.weight=weight 5 self.height=height 6 7 @property 8 def bmi(self): 9 return self.weight / (self.height**2) 10 11 p1=People('egon',75,1.85) 12 print(p1.bmi)
1 # 例二:圆的周长和面积 2 import math 3 4 class Circle: 5 def __init__(self,radius): #圆的半径radius 6 self.radius=radius 7 8 @property 9 def area(self): 10 return math.pi * self.radius**2 #计算面积 11 12 @property 13 def perimeter(self): 14 return 2*math.pi*self.radius #计算周长 15 16 c=Circle(10) 17 print(c.radius) 18 print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 19 print(c.perimeter) #同上 20 ''' 21 输出结果: 22 314.1592653589793 23 62.83185307179586 24 '''
为什么要用property
1 class Foo: # 方式一 2 @property 3 def AAA(self): 4 print('get的时候运行我啊') 5 6 7 @AAA.setter 8 def AAA(self,value): 9 print('set的时候运行我啊') 10 11 12 @AAA.deleter 13 def AAA(self): 14 print('delete的时候运行我啊') 15 16 17 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 18 f1=Foo() 19 f1.AAA 20 f1.AAA='aaa' 21 del f1.AAA
1 # 方式二 2 class Foo: 3 def get_AAA(self): 4 print('get的时候运行我啊') 5 6 7 def set_AAA(self,value): 8 print('set的时候运行我啊') 9 10 11 def delete_AAA(self): 12 print('delete的时候运行我啊') 13 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 14 15 16 f1=Foo() 17 f1.AAA 18 f1.AAA='aaa' 19 del f1.AAA
1 class Goods: 2 3 4 def __init__(self): 5 # 原价 6 self.original_price = 100 7 # 折扣 8 self.discount = 0.8 9 10 11 @property 12 def price(self): 13 # 实际价格 = 原价 * 折扣 14 new_price = self.original_price * self.discount 15 return new_price 16 17 18 @price.setter 19 def price(self, value): 20 self.original_price = value 21 22 23 @price.deleter 24 def price(self): 25 del self.original_price 26 27 28 29 30 obj = Goods() 31 obj.price # 获取商品价格 32 obj.price = 200 # 修改商品原价 33 print(obj.price) 34 del obj.price # 删除商品原价
3.2.5 classmethod
1 class Classmethod_Demo(): 2 role = 'dog' 3 4 5 @classmethod 6 def func(cls): 7 print(cls.role) 8 9 10 Classmethod_Demo.func()
3.2.6 staticmethod
1 class Staticmethod_Demo(): 2 role = 'dog' 3 4 5 @staticmethod 6 def func(): 7 print("当普通方法用") 8 9 10 Staticmethod_Demo.func()
3.3 多态
在需要使用父类对象的地方,也可以使用子类对象, 这种情况就叫多态.
比如, 在函数中,我需要调用 某一个父类对象的方法, 那么我们也可以在这个地方调用子类对象的方法.
如何使用多态:
1.子类继承父类
2.子类重写父类中的方法
3.通过对象调用这个方法
1 # 定义父类 2 class Father: 3 def cure(self): 4 print("父亲给病人治病...") 5 6 7 # 定义子类继承父类 8 class Son(Father): 9 # 重写父类中的方法 10 def cure(self): 11 print("儿子给病人治病...") 12 13 14 # 定义函数,在里面 调用 医生的cure函数 15 def call_cure(doctor): 16 17 # 调用医生治病的方法 18 doctor.cure() 19 20 21 # 创建父类对象 22 father = Father() 23 # 调用函数,把父类对象传递函数 24 call_cure(father) 25 26 27 # 创建子类对象 28 son = Son() 29 # 调用函数,把子类对象传递函数 30 call_cure(son)
多态的好处:
给call_cure(doctor)函数传递哪个对象,在它里面就会调用哪个对象的cure()方法,也就是说在它里面既可以调用son对象的cure()方法,也能调用father对象的cure()方法,当然了也可以在它里面调用Father类其它子类对象的cure()方法,这样可以让call_cure(doctor)函数变得更加灵活,额外增加了它的功能,提高了它的扩展性.
鸭子类型
四、面向对象的软件开发
面向对象的软件工程包括下面几个部:
面向对象的常用术语
抽象/实现