面向对象的程序设计
面向过程的程序设计经常用于操作系统的内核,git等,一成不变的流水线式解决一个问题,极大程度降低程序复杂性。
面向对象的程序设计解决了程序的扩展性(类可产生各种各样的对象,对于新增技能或修改技能可使用方法直接调用),但是可控性差,因为面向对象程序一旦开始就是由对象之间交互来解决问题。
OOD面向对象的程序设计
先找程序中所有的对象,将对象归纳出类(并归纳出共同属性与方法和不同的属性)。
OOP面向对象编程
编程时先定义出类,再根据类实例化出对象。
python3统一了类与类型的概念,python3中的类型就是类。
编程方式:
面向过程: 根据代码在脚本的堆叠顺序,从上到下依次执行,
函数式编程:将相同功能的代码封装到函数中,直接调用即可,减少代码重复性,
面向对象:对函数进行分类和封装,将同类的函数放到一个类中,使调用更简单。
类的简介
类和对象
类就是一个模板,模板里可以包含多个方法(即函数),方法里实现一些功能,对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。
#创建类 class+类名 class foo: #class是关键字,表示类,foo是类的名字 def f1(self): #类的方法1 pass def f2(self): #类的方法2 pass #创建对象 对象 = 类名() bar = foo() #创建一个bar对象 ,此对象中有类中所有的方法 ,创建对象,类名称后加括号即可 #调用对象的方法 对象.方法名() bar.f1() bar.f2()
类名+()就等于在执行Person.__init__(),执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
这里提一嘴特殊的类属性。
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类
类名.__bases__# 类所有父类构成的元组
类名.__dict__# 查看类和对象的名称空间
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
python中使用class定义类,在python3中只有新式类(默认继承object),而python2中有新式类(手写继承object)与经典类的区别,可使用__basic__查看继承关系。
python2的经典类:
类的一般操作形式:
class Teacher: #定义类 x=1 #调用属性时不论对象还是类指向的都是同一块内存地址,属性又称静态字段 def __init__(self,name,sex,age,money):#init不能有返回值,里面放的是独有特征,共有特征使用单独函数定义 self.name=name #普通字段 self.sex=sex self.age=age self.money=money def search(self):#teacher调用函数或者实例化对象的绑定方法调用的不是同一块内存地址,普通方法 print("scord") def study(self): print("study") print(Teacher.x) #1 t=Teacher('jeff','male','110',10)#实例化出来的对象 print(t.name) #jeff print(t.age) #110 t.study() #study Teacher.y=5 print(Teacher.y)#5
class 类名: 类属性 = None def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 def 方法名(self):pass def 方法名2(self):pass 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法 #括号里传参数,参数不需要传self,其他与init中的形参一一对应 #结果返回一个对象 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可 #对象增加属性 对象.新的属性名 = 1000
类是整个单独的名称空间,定义类也就是单独定义了他的名称空间(变量,函数和类名字)用来存储类中定义的所有名字,这些名字称为类的属性,静态属性就是直接在类中定义的变量,动态属性就是定义在类中的方法。可以使用__dict__查看类和对象的名称空间。实例化对象会先从自己的dict查找变量,找不到就去类(父类)的dict找,没有则会报错。
类可以进行实例化,可以进行属性引用。对象只能属性引用。对象本身只能引用属性(变量name,sex,age。。。)。
t.name2='tom'#增 del t.name2#删 t.name='jerry'#改 print(t.name)#查
实例化对象之间的交互:
class Riven: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 class Garen: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 #对象之间的交互 r1=Riven('芮雯雯') g1=Garen('草丛轮') print(r1.life_value) g1.attack(r1) print(r1.life_value)
私有属性
特点:
类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。
class people: __name = 'jeff' __age = 12 def set(self): print(self.__name) print(self.__age) p = people() #print(p.__name,p.__age) #AttributeError: 'people' object has no attribute '__name' p.set() 运行结果; jeff 12
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 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()#对于用户只要知道这个接口的功能就可以了
property属性
把绑定方法装饰的像一个属性一样调用,被property装饰的属性会优先于对象的属性被使用。
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 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) print(c.area) #可以像访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 ''' 输出结果: 314.1592653589793 62.83185307179586 ''' #注意:此时的特性area和perimeter不能被赋值 c.area=3 #为特性area赋值 ''' 抛出异常: AttributeError: can't set attribute '''
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。
对于obj.name我们想要在实际中会有需求能改他的值,实例.name='jeff'就是改self.__name。
这就需要用到@name.setter,删除特性的方法@name.deleter。
class People: def __init__(self,name,SEX): self.name=name # self.__sex=SEX self.sex=SEX #self.sex='male' p1.sex='male' @property def sex(self): return self.__sex #p1.__sex @sex.setter def sex(self,value): # print(self,value) if not isinstance(value,str): raise TypeError('性别必须是字符串类型') self.__sex=value #p1.__sex='male' @sex.deleter def sex(self): del self.__sex #del p1.__sex p1=People('cobila','male') p1.sex='female' print(p1.sex)
类方法
class people: country = 'china' # 类方法,用classmethod来进行修饰 @classmethod def getCountry(cls): #类方法自动将类作为cls传递进函数内 return cls.country @classmethod def setCountry(cls, country): cls.country = country p = people() print(p.getCountry()) # 可以用过实例对象引用 print(people.getCountry()) # 可以通过类对象引用 p.setCountry('japan') print(p.getCountry()) 运行结果: china china japan
静态方法
class people: country = 'china' @staticmethod # 静态方法使用装饰器方式绑定,并且作用和普通函数一样并不会自动传递self def getCountry(): return people.country print(people.getCountry()) 运行结果: china
普通方法 默认有一个self对象传进来,并且只能被对象调用——绑定到对象
类方法 默认有一个cls传进来表示本类,并且可以被类和对象(不推荐)调用——绑定到类
静态方法 没有默认参数,并且可以被类和对象(不推荐)调用——非绑定
面向对象三大特性
封装
封装顾名思义就是将东西装起来然后留出接口供用户使用,不需要知道内部发生了什么,只需要知道接口如何调用即可。在此之前所说的__init__定义的普通字段的赋值,并提供普通方法作为调用接口就是python中的封装结构。除此之外,私有属性也是封装的一种方法之一。
好处:将变化隔离;
便于使用;
提高复用性;
提高安全性;
封装原则:
将不需要对外提供的内容都隐藏起来;
把属性都隐藏,提供公共方法对其访问。
多重封装:
#创建类 class SQL: def __init__(self,name,passwd): self.name = name self.passwd = passwd def create(self,sql): print(sql) class test: def __init__(self,name,obj): self.name = name self.obj = obj def add(self,arg): print(arg) class test2: def __init__(self,obj): self.obj = obj def iner(slef,arg): print(arg) #创建对象 c1 = SQL('fuzj','12313') c2 = test('aaa',c1) #把c1对象封装到c2对象里,c2对象会有c1对象的所有方法 c3 = test2(c2) #把c2对象封装到c3对象中,c3对象会有c2对象的所有方法,同时也就有了c1对象的所有方法 #调用 c1.create("c1调用自身create方法") c2.obj.create('c2调用c1的create方法') c3.obj.add('c3调用c2的add方法') c3.obj.obj.create('c3调用c1的create方法') 结果: c1调用自身create方法 c2调用c1的create方法 c3调用c2的add方法 c3调用c1的create方法
继承
对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
一个类可以继承多个父类(基类,超类),这点与其他语言略有不同,当类是经典类时,多继承情况下,会按照深度优先方式查找;当类是新式类时,多继承情况下,会按照广度优先方式查找。
class D: def bar(self): print('D.bar') class C(D): def bar(self): print('C.bar') class B(D): def bar(self): print('B.bar') class A(B, C): def bar(self): print('A.bar') a = A() # 经典类执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
#新式类执行bar方法时的查找顺序为A --> B --> C --> D
class Hero:#这个英雄类包含了所有英雄都有的属性和方法 def __init__(self, nickname, aggressivity, life_value): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self, enemy): enemy.life_value -= self.aggressivity class Garen(Hero):#盖伦继承了英雄类 camp='Demacia' def attack(self, enemy): pass def fire(self):#盖伦独有的喷火技能 print('%s is firing' %self.nickname) class Riven(Hero): camp='Noxus' g1=Garen('garen',18,200) r1=Riven('rivren',18,200) # print(g1.camp) # print(r1.camp) # g1.fire() g1.attack(g1)
class Animal: #父类 def __init__(self,name,life_value,aggr): self.name = name self.life_value = life_value self.aggr = aggr #攻击力 def eat(self): self.life_value += 10 class Person(Animal): #子类 def __init__(self,money,name,life_value,aggr): super().__init__(name,life_value,aggr)#super调用父类的构造方法 self.money = money #派生属性 def attack(self,enemy): #人的派生方法 enemy.life_value -= self.aggr class Dog(Animal): #派生子类 def __init__(self,breed,name,life_value,aggr): #Animal.__init__(self,name,life_value,aggr) #让子类执行父类的方法,就是父类名.方法名(参数),连self也得传就是super() super().__init__(name,life_value,aggr) #super关键字——新式类 #super(Dog,self).__init__(name,life_value,aggr) #super关键字——新式类 self.breed = breed def bite(self,person): #狗的派生方法 person.life_value -= self.aggr def eat(self): # 父类方法的重写 super().eat() print('dog is eating~~~ ') ha2 = Dog('牛头梗','旺财',20000,100) print(ha2.life_value) ha2.eat() print(ha2.life_value) # super(Dog,ha2).eat() #调用父类的 print(ha2.life_value)
class Hero: def __init__(self, nickname, aggressivity, life_value): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self, enemy): print('Hero attack') enemy.life_value -= self.aggressivity # print(Hero.__init__) # print(Hero.attack) class Garen(Hero): camp = 'Demacia' def __init__(self, nickname, aggressivity, life_value, script): Hero.__init__(self,nickname,aggressivity,life_value) # self.nickname = nickname # self.aggressivity = aggressivity # self.life_value = life_value self.script = script def attack(self, enemy): # self=g1,enemy=r1 # self.attack(enemy) #g1.attack() Hero.attack(self, enemy) print('from garen attack') def fire(self): print('%s is firing' % self.nickname) # g1=Garen('garen',18,200) #Garen.__init__(g1,'garen',18,200) g1=Garen('garen',18,200,'人在塔在') #Garen.__init__(g1,'garen',18,200) print(g1.script)
多态
多态即多种形态,在运行时确定其状态,在编译阶段无法确定其类型,这就是多态。Python中的多态和Java以及C++中的多态有点不同,Python中的变量是弱类型的,在定义时不用指明其类型,它会根据需要在运行时确定变量的类型(个人觉得这也是多态的一种体现),并且Python本身是一种解释性语言,不进行预编译,因此它就只在运行时确定其状态,故也有人说Python是一种多态语言。
组合
与继承不同,组合不是什么是什么的关系,而是一种什么有什么的关系,比如学生有课程,他们是一种并行的关系而不是可以继承的关系。
class Student: def __init__(self,ID,name,sex): self.id=ID self.name=name self.sex=sex self.course_list=[] class Course: def __init__(self,name,price,period): self.name=name self.price=price self.period=period s1=Student('123123123123','cobila','female') python_obj=Course('python',1,'7m') linux_obj=Course('linux',1,'2m') s1.course_list.append(python_obj) s1.course_list.append(linux_obj) print(s1.course_list[0].name) print(s1.course_list[0].price) 运行结果: python 1
关于组合的问题看到一个哥们在博客里的人狗大战的代码,与德玛西亚大战瑞文一样,可以有一个装备类,装备有技能,也有属性,可以通过购买装备组合,也就是人与狗的实例化对象都可以拥有装备,装备加属性加技能再进行交互。
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 print("{0}打了{1}一下,{1}剩余血量{2}".format(self.name, dog.name, dog.life_value)) 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 print("{0}咬了{1}一下,{1}剩余血量{2}".format(self.name,people.name,people.life_value)) 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 -= 3000 # 假设攻击力是3000 print("{0}发动主动技:蛟龙==>{1}剩余血量{2}".format(self.name, obj.name, obj.life_value)) a = Person("苍井井",10,1000) b = Dog("egon","狼狗",200,20000) c = Weapon("蛟龙鞭",1000,40,2000) a.money = 2000 #判断是否买的起武器 if a.money > c.price : c.update(a) a.weapon = c #大战开始 while True : a.attack(b) if b.life_value <= 0 : print(b.name + "被" + a.name + "打死了!") break a.weapon.prick(b) if b.life_value <= 0 : print(b.name + "被" + a.name + "绞死了!") break b.bite(a) if a.life_value <= 0 : print(a.name+"被"+b.name+"咬死了!") break
接口归一化设计
父类中定义不操作,子类进行定义的衍生(子类都有相同功能,并且功能操作可能不一样)。如果子类没有定义或者也pass了,那这个方法就没有实际的意义了,为了防止这种现象的出现要采取一些措施。
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(Interface): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') t1=Txt() s1=Sata() p1=Process() t1.read() t1.write() s1.read() s1.write() p1.read() p1.write()
class Animal: def run(self): raise AttributeError('子类必须实现这个方法') def speak(self): raise AttributeError('子类必须实现这个方法') class People(Animal): def run(self): print('人正在走') # def speak(self): # print('说话') class Pig(Animal): def run(self): print('pig is walking') def speak(self): print('哼哼哼') peo1=People() # peo1.run() peo1.speak()#异常咯
这使得虽然大家的操作都不一样,但是调用的时候方法都是一样的,这就是归一化设计。
这里存在一个问题,我们需要进行拓展,如果说,我们的子类没有进行父类的某一项普通方法的定义,根据广度优先的查找顺序,会在父类中找到此方法,但是此方法没有任何定义,自然这个操作毫无意义,所以我们可以在父类进行raise主动将异常抛出,或者使用abc模块自动抛出异常。
import abc #抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们 class Animal(metaclass=abc.ABCMeta): tag='123123123123123' @abc.abstractmethod def run(self): pass @abc.abstractmethod def speak(self): pass class People(Animal): # def run(self): #子类没有定义父类有的方法,但是没有搜寻父类的方法直接报错 # pass def speak(self): pass peo1=People() print(peo1.tag) 运行结果: TypeError: Can't instantiate abstract class People with abstract methods run
注意:抽象类不能被实例化
super()函数的用法
super在python2中的用法:
1:super(自己的类,self).父类的函数名字
2:super只能用于新式类
class People(object): def __init__(self,name,sex,age): self.name=name self.age=age self.sex=sex def walk(self): print('%s is walking' %self.name) class Chinese(People): country='China' def __init__(self,name,sex,age,language='Chinese'): # self.name=name # self.sex=sex # self.age=age # People.__init__(self,name,sex,age) super(Chinese,self).__init__(name,sex,age) self.language=language c=Chinese('egon','male',18) print c.name,c.age,c.sex,c.language
class People: def __init__(self,name,sex,age): self.name=name self.age=age self.sex=sex def walk(self): print('%s is walking' %self.name) class Chinese(People): country='China' def __init__(self,name,sex,age,language='Chinese'): # self.name=name # self.sex=sex # self.age=age # People.__init__(self,name,sex,age) super().__init__(name,sex,age) self.language=language def walk(self,x): super().walk() print('子类的x',x) c=Chinese('egon','male',18) # print(c.name,c.age,c.sex,c.language) c.walk(123)
__str__
_str__定义在类内部,必须返回一个字符串类型,打印由这个类产生的对象时,会触发执行.
class People: def __init__(self,name,age): self.name=name self.age=age def __str__(self): return '<name:%s,age:%s>' %(self.name,self.age) p1=People('egon',18) print(p1) str(p1) #此时print触发----->p1.__str__() #所以打印结果为<name:egon,age:18>