面向过程VS面向对象
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
面向对象的程序设计的
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物(人、狗、老虎)
对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
实例化:类——>对象的过程(这在生活中表现的不明显,我们在后面再慢慢解释)
初识类和对象
python中一切皆为对象,类型的本质就是类
类的相关知识
一 初识类
声明
1 def functionName(args): 2 '函数文档字符串' 3 函数体
1 ''' 2 class 类名: 3 '类的文档字符串' 4 类体 5 ''' 6 7 #我们创建一个类 8 class Data: 9 pass
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法,也叫动态属性 print("person is walking...")
二 类有两种作用:属性引用和实例化
1.使用类中的名字
# 查看类中的名字:
# 类名.变量名 # 可以修改变量的值
# 类名.__dict__['变量名'] # 不能修改
属性引用(类名,属性)
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
2.创造对象 实例化对象
# 对象就是实例,一个实际的例子
# 对象 = 类名()
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name): self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化的过程就是类——>对象的过程
原本我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。
语法:对象名 = 类名(参数)
egg = Person('egon') #类名()就等于在执行Person.__init__() #执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
查看属性 调用方法
print(egg.name) #查看属性直接 对象名.属性名 print(egg.walk()) #调用方法,对象名.方法名()
关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。
因为你瞎改别人就不认识
类属性的补充
1 一:我们定义的类的属性到底存到哪里了?有两种方式查看 2 dir(类名):查出的是一个名字列表 3 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 4 5 二:特殊的类属性 6 类名.__name__# 类的名字(字符串) 7 类名.__doc__# 类的文档字符串 8 类名.__base__# 类的第一个父类(在讲继承时会讲) 9 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 10 类名.__dict__# 类的字典属性 11 类名.__module__# 类定义所在的模块 12 类名.__class__# 实例对应的类(仅新式类中)
对象的相关知识
人狗大战
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
对象是关于类而实际存在的一个例子,即实例
对象/实例只有一种作用:属性引用
egg = Person('egon',10,1000) print(egg.name) print(egg.aggressivity) print(egg.life_value)
当然了,你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。
引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号
print(egg.attack)
面向对象小结--定义及调用的固定模式
1 class 类名: 2 def __init__(self,参数1,参数2): 3 self.对象的属性1 = 参数1 4 self.对象的属性2 = 参数2 5 6 def 方法名(self):pass 7 8 def 方法名2(self):pass 9 10 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 11 #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法 12 #括号里传参数,参数不需要传self,其他与init中的形参一一对应 13 #结果返回一个对象 14 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 15 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
对象之间的交互
创建person类 dog类
1 class Person: # 类名 Person 2 role = '人' # 类的静态变量 是所有的对象共享的一个属性 3 def __init__(self,name,sex,aggr,hp): #方法 动态属性 内置的双下方法 4 self.name = name # 对象属性 实例属性 5 self.sex = sex 6 self.aggr = aggr 7 self.hp = hp 8 def attack(self,dog): # 自定义方法 9 print('%s打了%s'%(self.name,dog.name)) 10 dog.hp -= self.aggr
class Dog: def __init__(self,name,kind,aggr,hp): self.name = name # 对象属性 self.kind = kind self.aggr = aggr self.hp = hp def bite(self,person): print('%s咬了%s'%(self.name,person.name)) person.hp -= self.aggr
实例化 person dog
hei = Dog('小黑','teddy',260,10000) # 实例化: 先创造对象 再初始化 alex = Person('alex','female',1,250) # 实例化 egon = Person('egon','male',2,500) # 实例化
交互
alex.attack(hei) # Person.attack(alex,hei) egon.attack(hei) # Person.attack(alex,hei)
1 # 圆 类 2 # 属性 半径 #对象的属性 3 # 方法 计算圆面积 pi*r**2 圆周长 2pir 4 from math import pi 5 class Circle: 6 def __init__(self,r): 7 self.r = r 8 def area(self): 9 return pi*(self.r**2) 10 def perimeter(self): 11 return pi*2*self.r 12 13 c1 = Circle(5) 14 print(c1.r) 15 print(c1.area()) 16 print(c1.perimeter())
类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
-
静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的
>>>id(egg.role) 4341594072 >>>id(Person.role) 4341594072
而类的动态属性是绑定到所有对象的
>>>egg.attack <bound method Person.attack of <__main__.Person object at 0x101285860>> >>>Person.attack <function Person.attack at 0x10127abf8>
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
# 命名空间 class Person: Country = '中国人' # 静态变量 # print(Person.Country) # alex = Person() # 创建了一个空的命名空间 # alex.name = 'alex' # 对象 # alex.Country = '泰国人' # egon = Person() # egon.name = 'egon' # 类名.静态变量 对象.属性名 # 类名可以调用对象的属性么? 不可以 # 对象可以调用类中的属性么? 可以 # print(Person.Country) # print(alex.Country) # print(egon.Country) # 由于对象和类之间存在一个关联关系 # 所以对象能够找到类 # 但是 类不能找到对象 #使用类名.属性 只会寻找类中的静态变量名字 #使用对象.属性 会现在对象自己的命名空间中找名字 # 如果找不到 再到类的内存空间中去找
面向对象的组合用法
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
1 # 组合 两个类的事儿 2 # 什么叫组合 : 一个类对象的属性 是 另外一个类的对象 3 # 两个类的事儿 :类与类之间有一种"什么有什么的关系" 4 5 # 圆的类 6 # 圆环 和 圆 7 # 圆环 也是一个类 8 # 属性 大圆半径 和 小圆半径 9 # 圆环 求面积 求周长 10 11 # 圆环 12 from math import pi 13 class Circle: 14 def __init__(self,r): 15 self.r = r 16 def area(self): 17 return pi*(self.r**2) 18 def perimeter(self): 19 return 2*pi*self.r 20 21 # c1 = Circle(5) 22 # c1.perimeter() 23 class Ring: 24 def __init__(self,outer,inner): 25 self.outer = Circle(outer) # 组合 26 self.inner = Circle(inner) 27 28 def area(self): 29 return self.outer.area() - self.inner.area() 30 31 def perimeter(self): 32 return self.outer.perimeter() + self.inner.perimeter() 33 r = Ring(10,5) 34 print(r.area()) 35 print(r.perimeter())
1 # 老师 name sex course(课程) birth 2 # 生日 年月日 3 class Birthday: 4 def __init__(self,year,month,day): 5 self.year = year 6 self.month = month 7 self.day = day 8 class Teacher: 9 def __init__(self,name,sex,course,birth): 10 self.name = name 11 self.sex = sex 12 self.course = course 13 self.birth = birth # birth是一个对象 14 15 birth = Birthday(1960,3,7) # birth 是Birthday类的一个对象 16 alex = Teacher('alex','male','python',birth) 17 print(birth.day) 18 print(alex.name) 19 20 21 print(birth) 22 import time 23 if birth.month == time.localtime().tm_mon and 24 birth.day == time.localtime().tm_mday: 25 print('生日快乐') 26 27 print(time.localtime().tm_year - birth.year)
初识面向对象小结
# 命名空间 # 类和对象之间的关系 : 对象(类对象指针) --> 类 # 在对象使用名字的时候 : 如果对象自己有就用自己的,自己没有就用类的 # 对象对于类中的变量不能重新赋值,如果重新赋值,就会在对象自己的内存中创建一个同名的新变量. # 静态变量 # 是所有的对象共享的值 # 使用的时候,最好全部用类名调用 # 组合 # 一个类的对象作为另一个类对象的属性 # 什么有什么的关系 # 自定义的类之间的组合 # class A:pass # a = A() # a.n = 1 # A 和 int # a.l = [] # 之所以用到组合 是希望能使用a对象去操作另外一个类对象中的方法 # a.l.append()
面向对象的三大特性
继承
什么是继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
继承与抽象(先抽象在继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
- .将奥巴马和梅西这俩对象比较像的部分抽取成类;
- 将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
人狗大战
class Animal: # 动物 def __init__(self,name,aggr,hp): #方法 动态属性 内置的双下方法 self.name = name # 对象属性 实例属性 self.aggr = aggr self.hp = hp class Person(Animal): # 类名 Person def __init__(self,name,sex,aggr,hp): self.sex = sex # 派生属性 Animal.__init__(self,name,aggr,hp) def attack(self,dog): # 自定义方法 print('%s打了%s'%(self.name,dog.name)) dog.hp -= self.aggr class Dog(Animal): def __init__(self,name,kind,aggr,hp): self.kind = kind # 派生属性 Animal.__init__(self,name,aggr,hp) def bite(self,person): print('%s咬了%s'%(self.name,person.name)) person.hp -= self.aggr # alex = Person('alex','不详',1,250) # hei = Dog('小黑','teddy',260,10000) # alex.attack(hei) # hei.bite(alex)
首先创建一个Person的对象,
初始化:找init方法,自己有调用自己的
父类和子类拥有同名的方法时,子类的对象只会调用子类的
如果想要调用父类的方法,需要 父类名.方法名(self,其他参数)
super关键字
# super class Animal: # 动物 def __init__(self,name,aggr,hp): #方法 动态属性 内置的双下方法 self.name = name # 对象属性 实例属性 self.aggr = aggr self.hp = hp def eat(self): print('in Animal eat') class Person(Animal): # 类名 Person def __init__(self,name,sex,aggr,hp): self.sex = sex # 派生属性 # Animal.__init__(self,name,aggr,hp) super().__init__(name,aggr,hp) # 单继承中 super会寻找父类 # 且在使用super调用父类方法的时候不需要再传self参数 def eat(self): print('in Person eat') # Animal.eat(self) # super().eat() alex = Person('alex','不详',1,250) print(alex.__dict__) # Animal.eat(alex) # super(Person,alex).eat()
单继承
class A: def wahaha(self):print('in A') class B(A): def wahaha(self):print('in B') class C(B): def wahaha(self):print('in C') class D(C):pass # def wahaha(self):print('in D') d = D() d.wahaha() #结果 in C # 不要发生循环继承 # 依赖倒置原则 : # 高层模块不应该依赖低层模块
多继承
class A:pass # def qqxing(self): # print('in A') class B: def qqxing(self): print('in B') class C: def qqxing(self): print('in C') class D(B,A,C): pass # def qqxing(self): # print('in D') d = D() d.qqxing() # 结果 in B
钻石继承
class A: def wangwang(self): print('in A') class B(A): def wangwang(self): super().wangwang() # 'in C ' print('in B') class C(A): def wangwang(self): print('in C') class D(B,C):pass d = D() d.wangwang() #in C #in B
class A: def wangwang(self): print('in A') class B(A):pass # def wangwang(self): # print('in B') class C(A):pass # def wangwang(self): # print('in C') class D(B):pass # def wangwang(self): # print('in D') class E(C):pass # def wangwang(self): # print('in E') class F(D,E):pass # def wangwang(self): # print('in F') f = F() f.wangwang() print(F.mro()) # in A # [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# 如果一个类 继承了object 这个类就被称为新式类 # 没有继承object类 就被称为经典类 # 深度优先 广度优先 都是一种遍历算法,把这个图中所有的项都走一遍,且不会重复 #经典类 遵循 深度优先算法 且没有mro方法 python2 #新式类 遵循 广度优先算法 有mro方法 py2 py3 # py3 super # 单继承中 super就是找父类 # 多继承 super寻找的轨迹是根据mro(广度优先)顺序的
小结
# 面向对象的三大特性 : 继承 多态 封装 # 继承 # 为什么要继承 # 减少代码的重用性 # class Child(Parent): 继承的语法 # python 支持单继承和多继承 # 非常重要的概念 : 子类或者子类的对象 使用 名字, # 自己有就不用别人的,自己没有找父类 # 父类 :基类 超类 # 子类 :派生类 # 当子类和父类同时拥有一个名字的时候,需要同时使用这两个名字: # 父类.名字(self) # super().名字() # 新式类和经典类的继承问题 # python3 只有新式类 super只在python3中生效 # python2 新式类和经典类的区别: 新式类继承object # 新式类和经典类之间的区别: #1.在继承的查找顺序上 :经典类深度优先 新式类广度优先 #2.新式类有mro方法来标志广度优先的顺序 #3.新式类继承object
接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
1 # java里 2 from abc import ABCMeta,abstractmethod 3 # 接口类 : 接口类就是一个规范 接口类一般是项目设计人员写好的 4 class Payment(metaclass=ABCMeta): #规范 5 @abstractmethod 6 def pay(self,money): 7 pass 8 # raise NotImplementedError 9 10 class ApplePay(Payment): 11 def pay(self,money):pass 12 app = ApplePay() 13 # 微信支付 14 class WeChatPay(Payment): 15 def pay(self,money): 16 print('通过微信支付了%s元钱'%(money)) 17 18 # 支付宝支付 19 class AliPay(Payment): 20 def pay(self,money): 21 print('通过支付宝支付了%s元钱'%(money)) 22 23 # apple pay 24 class ApplePay(Payment): 25 def pay(self,money): 26 print('通过支付宝支付了%s元钱' % (money)) 27 28 wp = WeChatPay() 29 alp = AliPay() 30 app = ApplePay() 31 def pay(pay_obj,money): # 程序的归一化设计 32 pay_obj.pay(money) 33 34 pay(alp,100) 35 pay(wp,100) 36 pay(app,100)
1 接口类是一个规范 2 多种支付方式,每一种支付方式都是一个类, 3 每一个类中定义一个支付方法完成支付功能 4 由于每一种支付方式在程序中表现出来的就是支付类的对象 5 拿到每一个支付对象都需要调用支付方法 6 为了方便支付方法的调用 7 需要统一一个入口 pay函数 8 9 Interface 10 在python里没有接口类这种数据类型,没有接口类专门的语法 11 但是 可以通过继承abc模块实现接口的功能
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
抽象类
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的
1 # 抽象类 规范一个类的类 2 # 在python里 抽象类和接口类 没区别 3 # word文档是文件 4 # excle是文件 5 # ppt是文件 6 from abc import ABCMeta , abstractmethod 7 class File(metaclass=ABCMeta): 8 @abstractmethod 9 def read(self): 10 f = open('file','r') 11 self.f = f 12 @abstractmethod 13 def write(self): 14 f = open('file','w') 15 16 class Word(File): 17 def read(self): 18 # 打开一个文件 19 # 读文件 20 # 关闭一个文件 21 super().read() 22 self.f.read() 23 self.f.close() 24 25 class Excle(File): 26 def read(self): 27 # 打开一个文件 28 # 读某一列 读某一列 29 # 关闭一个文件 30 super().read() 31 self.f.readline() 32 self.f.close() 33 34 class PPT(File): 35 def read(self): 36 # 打开一个文件 37 # 读文字 读表格 读图片 38 # 关闭一个文件 39 super().read() 40 self.f.readline() 41 self.f.close()
接口类与抽象类
1 在java里 有区别 2 java的接口规定里面的方法一定不能实现(一句代码也不能写) 3 抽象类 单继承 4 5 无论接口类 还是抽象类 其实都是一种面向对象编程的开发规范 6 只是在接口类或者抽象类中 去约束继承它的子类必须实现某些方法 7 对于java代码来说:如果发生多继承 那么一定是接口类 且里面的方法都不能实现 8 如果在方法里有了实现 那么一定是单继承 的抽象类 9 但是对于python来说 就没有这些约束 10 因为python没有接口的概念 11 对于类的继承 没有多继承的限制 12 实际上abc模块是帮我实现抽象类的方法,只是我们用它来模仿接口类的效果了 13 14 在python中,只要metaclass = ABCMeta 定义了抽象方法(@abctractmethod) 15 这个类就不能被实例化 16 你可以说他是一个抽象类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
继承小结
继承的作用
1 减少代码的重用 2 提高代码可读性 3 规范编程模式
几个名词
1 抽象:抽象即抽取类似或者说比较像的部分。是一个从具题到抽象的过程。 2 继承:子类继承了父类的方法和属性 3 派生:子类在父类方法和属性的基础上产生了新的方法和属性
接口类和抽象类
1 1.多继承问题 2 在继承抽象类的过程中,我们应该尽量避免多继承; 3 而在继承接口的时候,我们反而鼓励你来多继承接口 4 5 6 2.方法的实现 7 在抽象类中,我们可以对一些抽象方法做出基础实现; 8 而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
钻石继承
1 新式类:广度优先 2 经典类:深度优先
多态
多态指的是一类事物有多种形态
python是自带多态的
动物有多种形态:人,狗,猪
1 import abc 2 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 3 @abc.abstractmethod 4 def talk(self): 5 pass 6 7 class People(Animal): #动物的形态之一:人 8 def talk(self): 9 print('say hello') 10 11 class Dog(Animal): #动物的形态之二:狗 12 def talk(self): 13 print('say wangwang') 14 15 class Pig(Animal): #动物的形态之三:猪 16 def talk(self): 17 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')
支付
from abc import ABCMeta,abstractclassmethod class payment(metaclass=ABCMeta): @abstractclassmethod def pay(self,money):pass class wechatpay(payment): def pay(self,money): print('通过微信支付了%s元钱'%(money)) def pay(pay_obj,money): # 程序的归一化设计 pay_obj.pay(money) we = wechatpay() pay(we,1000)
多态性
一 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
多态性
peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
1 #二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 2 class TxtFile: 3 def read(self): 4 pass 5 6 def write(self): 7 pass 8 9 class DiskFile: 10 def read(self): 11 pass 12 def write(self): 13 pass
小结
1 # 继承 (了解) 2 # 抽象类和接口类 from abc import ABCMeta,abstractmethod 3 # python中没有内置 接口的概念,所有的抽象类和接口类都是用来规范子类的 4 # 接口类和抽象类不能被实例化 5 # 多态 (了解) 6 # java中的多态和python中的鸭子类型 7 # 在某些强数据类型语言中, 8 # 归一化设计(函数的参数的问题上),需要传递参数的数据类型 9 # 为了几个类的对象都可以作为参数传递给函数, 10 # 就给这几个类创建一个父类,这样的话让父类成为这个参数的数据类型 11 # 子类的所有对象就都能够作为参数传递了 12 # 在python中,传递参数的时候,是弱数据类型特征,不需要指定参数的数据类型 13 # 鸭子类型 (list tuple str) 14 # 多个有相似特征的数据类型之间,不通过继承建立关系. 15 # 而是通过一种约定俗成的制度来约束 16 # list tuple str ---> 序列
封装
# 广义上的封装 : 属于一个类的静态和动态属性 总是出现在一个类中
# 使用的使用永远用类名或者对象名调用
# 狭义上的封装 : 就是把变量方法私有化,在类的外部以及子类中不能直接使用了
【封装】
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
1. 将变化隔离;
2. 便于使用;
3. 提高复用性;
4. 提高安全性;
【封装原则】
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
私有变量
1 class A: 2 STATIC = 'aaa' # 静态变量 3 __S = 'bbb' # 私有的静态变量 4 def wahaha(self): 5 print(A.__S) # _A__S 6 print(A.STATIC) 7 print(A.__dict__) 8 print(A._A__S) # 在类的外面调用私有的变量 9 a = A() 10 a.wahaha() 11 A.__B = 'ccc' #在类的外部添加了一个静态变量 12 print(A.__dict__) #我们不能在一个类的外部创建一个"私有的"变量 13 print(A.__B) 14 15 class B: 16 def __init__(self,name,pwd): 17 self.name = name 18 self.__pwd = pwd # 也可以创建对象的私有属性 19 20 def qqxing(self): 21 self.__a = 'A' # _B__a 22 23 def get_pwd(self): 24 print(self.__pwd) 25 b = B('alex','alex3714') 26 b.qqxing() 27 28 print(b.name) 29 print(b._B__pwd) # 当在类外部的时候,我们也不能直接使用对象的私有属性 30 b.get_pwd()
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__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._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
私有方法
class C: def __ppt(self): # 私有的方法 print('ppt') self.age = 83 def open(self): self.__ppt() print('打开文件') c = C() c._C__ppt() # 不能直接调用 c.open() c.name = 'alex' print()
私有的 静态变量 对象属性 方法
私有的 只能在类的内部定义和使用
__名字
在类外部使用私有静态变量 _类名__私有的名字
私有的名字 不能被子类继承
1 class D: 2 __DDD = 'ddd' # _D__DDD 3 AAA = 'aaa' 4 5 class E(D): 6 def qqxing(self): 7 print(E.AAA) #E._E__DDD 8 9 e = E() 10 e.qqxing() #aaa
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
# 房间类 : 所有人 价格 面积 class Room: def __init__(self,owner,price,length,width,height): self.owner = owner self.__price_single = price #单价 self.__length = length self.__width = width self.height = height def get_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__length * self.__width def get_price(self): return self.__price_single * self.get_area() alex = Room('alex',1000000,2,1,0.8) print(alex.get_area())#使用者调用接口get_area print(alex.get_price())
property属性
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
1 class Person: 2 def __init__(self,name,height,weight): 3 self.name = name 4 self.__height = height 5 self.__weight = weight 6 7 @property #将一个方法伪装成属性 8 def bmi(self): 9 return self.__weight / (self.__height**2) 10 11 wang = Person('王子',1.77,69) 12 print(wang.bmi)
1 from math import pi 2 class Circle: 3 def __init__(self,r): 4 self.r = r 5 @property 6 def area(self): 7 return self.r*self.r*pi 8 @property 9 def perimeter(self): 10 return 2*pi*self.r 11 12 c = Circle(10) 13 print(c.area) 14 print(c.perimeter)
1 class Goods: 2 def __init__(self,name,price,discount): 3 self.name = name 4 self.__price = price 5 self.discount = discount 6 7 @property 8 def price(self): 9 return self.__price * self.discount 10 11 apple = Goods('apple',5,0.8) 12 print(apple.price)
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('%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' del f.name #抛出异常'TypeError: Can not delete'
一个静态属性property本质就是实现了get,set,delete三种方法
1 class Foo: 2 @property 3 def AAA(self): 4 print('get的时候运行我啊') 5 6 @AAA.setter 7 def AAA(self,value): 8 print('set的时候运行我啊') 9 10 @AAA.deleter 11 def AAA(self): 12 print('delete的时候运行我啊') 13 14 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 15 f1=Foo() 16 f1.AAA 17 f1.AAA='aaa' 18 del f1.AAA
1 class Foo: 2 def get_AAA(self): 3 print('get的时候运行我啊') 4 5 def set_AAA(self,value): 6 print('set的时候运行我啊') 7 8 def delete_AAA(self): 9 print('delete的时候运行我啊') 10 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 11 12 f1=Foo() 13 f1.AAA 14 f1.AAA='aaa' 15 del f1.AAA
classmethod
1 class Goods: 2 __discount = 0.8 3 def __init__(self,name,price): 4 self.name = name 5 self.__price = price 6 7 @property 8 def price(self): 9 return self.__price*Goods.__discount 10 11 @classmethod #讲一个普通方法装饰为一个类方法 12 def change_discount(cls,new_dis): # 类方法 13 cls.__discount = new_dis 14 Goods.change_discount(1) 15 cig = Goods('cigrette',20) 16 print(cig.price) 17 cig.change_discount(0.2) 18 print(cig.price) 19 # cig.change_discount(1) 20 # print(cig.price) 21 22 #类方法是被@classmethod装饰的特殊方法 23 #被装饰之后,方法默认接收一个 类 作为参数 24 # 之后所有的操作都只能和 类中的静态变量相关 而不应该和对象相关 25 # 类名 和 对象名 都可以直接调用类方法
staticmethod
class Student: def __init__(self,name): self.name = name @staticmethod #装饰一个不需要self参数 也不需要cls参数的函数 def login(a,b,c): # 普通的函数 usr = input('username>>>') pwd = input('password>>>') if usr == 'alex' and pwd == '123': obj = Student(usr) return obj # 学生 login # 用户名 密码 属性 ret = Student.login(1,2,3) print(ret)
面向对象的更多说明
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
在一个大型软件开发过程中,OOP只是很小的一个部分。
对于全栈开发的你来说,这五个阶段都有了,对于简单的问题,不必严格按照这个5个阶段进行,往往由程序设计者按照面向对象的方法进行程序设计,包括类的设计和程序的设计
面向对象常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__