面向过程的程序设计经常用于操作系统的内核,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 类名:通用的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:property属性
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:low主动抛异常
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):py2super
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:py3super
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:__str__
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>