python-面向对象
面向对象
一 面向对象 简单认识
创建类和对象
面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。
类就是一个模板,模板里可以包含多个函数,函数里实现一些功能
对象则是根据模板创建的实例,通过实例对象可以执行类中的函数
一、面向过程:面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点:极大地降低了写成学的复杂度,只需要顺着执行的步骤,堆叠代码即可
缺点:一套流水线或者流程就是用来解决一个问题,如果修改代码就都得改变
二、面向对象:上帝的思想
优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
三、类:是具有相同特征的一类事物(人,狗,老虎)
对象/实例:具体的某一事物
实例化:类到对象的过程(实例=类名(参数1,参数2)
四 。类的两种作用:属性引用和实例化
class Person(objekt): role = '中国人' //类的静态属性 def __init__(self,name,sex): self.name=name self.sex=sex def walk(self): //这里的函数叫做方法-----动态属性 print('person is walking') hy = Person('kefeng',21) #实例化:类名(参数1,参数2) 类名()就等于在执行Person.__init__() print(hy.name) #直接查看属性 对象名.属性名 print(hy) #返回的是一个对象 hy.walk() #调用方法 ,对象名.方法() print(Person.role) #查看类属性
五、关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给它起个别的名字,但是一般正常人都不会这么做,因为你改了别人就不认识了。
注意:def __init__(self): 这句话可以写也可以不写,只要有参数参进来的时候就必须得写
def 方法名(self):这里的self必须得写
六、特殊的类属性
print(Person.__name__,type(Person.__name__)) #查看类的名字(这里的类名是字符串类型的)
print(Person.__dict__) #查出的是一个字典,key为属性名,value为属性值
print(Person.__doc__)# 就是把注释显示出来
print(dir(Person)) #查看类的方法,以列表的形式显示出来
print(Person.__module__) #类定义所在的模块
print(Person.__class__)
print(isinstance(hy,Person))#判断对象 是否是类的实例
七、对象的相关知识
1.对象是关于类而实际存在的一个例子,即实例
2.对象/实例只有一种作用:属性引用
class 类名: def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 def 方法名(self):pass def 方法名2(self):pass 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法 #括号里传参数,参数不需要传self,其他与init中的形参一一对应 #结果返回一个对象 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可 小结
class Person(object): def __init__(self,name,aggr,life_value): self.name=name self.aggr=aggr self.life_value=life_value def attack(self,dog): #人可以攻击狗 dog.life_value=dog.life_value-self.aggr class Dog(object): def __init__(self,name,aggr,life_value): self.name = name self.aggr = aggr self.life_value = life_value def attack(self,egg): #狗也可以攻击人 egg.life_value = egg.life_value - self.aggr egg = Person('egon',250,1000) dog = Dog('乐乐',50,2000) print('狗没攻击前egon的生命值:',dog.life_value) #没攻击前egon的生命值 egg.attack(dog) #让egon去攻击狗乐乐 print('狗攻击后egon的生命值:',dog.life_value) #攻击后egon的生命值 print('egon没攻击前狗的生命值:',egg.life_value) #没攻击前egon的生命值 dog.attack(egg) #让狗去攻击egon print('egon攻击后狗的生命值:',egg.life_value) #攻击后egon的生命值
八、类命名空间与对象,实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字成为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
二 面向对象三大特性
一、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
第一步:将内容封装到某处

elf 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
class Foo:(obj) def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('wupeiqi', 18) print obj1.name # 直接调用obj1对象的name属性 print obj1.age # 直接调用obj1对象的age属性 obj2 = Foo('alex', 73) print obj2.name # 直接调用obj2对象的name属性 print obj2.age # 直接调用obj2对象的age属性
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print self.name print self.age obj1 = Foo('wupeiqi', 18) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo('alex', 73) obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
练习一:在终端输出如下信息
- 小明,10岁,男,上山去砍柴
- 小明,10岁,男,开车去东北
- 小明,10岁,男,最爱大保健
- 老李,90岁,男,上山去砍柴
- 老李,90岁,男,开车去东北
- 老李,90岁,男,最爱大保健
-
函数式def kanchai(name, age, gender): print "%s,%s岁,%s,上山去砍柴" %(name, age, gender) def qudongbei(name, age, gender): print "%s,%s岁,%s,开车去东北" %(name, age, gender) def dabaojian(name, age, gender): print "%s,%s岁,%s,最爱大保健" %(name, age, gender) kanchai('小明', 10, '男') qudongbei('小明', 10, '男') dabaojian('小明', 10, '男') kanchai('老李', 90, '男') qudongbei('老李', 90, '男') dabaojian('老李', 90, '男')
上述对比可以看出,如果使用函数式编程,需要在每次执行函数时传入相同的参数,如果参数多的话,又需要粘贴复制了... ;而对于面向对象只需要在创建对象时,将所有需要的参数封装到当前对象中,之后再次使用时,通过self间接去当前对象中取值即可
类方法class Foo: def __init__(self, name, age ,gender): self.name = name self.age = age self.gender = gender def kanchai(self): print "%s,%s岁,%s,上山去砍柴" %(self.name, self.age, self.gender) def qudongbei(self): print "%s,%s岁,%s,开车去东北" %(self.name, self.age, self.gender) def dabaojian(self): print "%s,%s岁,%s,最爱大保健" %(self.name, self.age, self.gender) xiaoming = Foo('小明', 10, '男') xiaoming.kanchai() xiaoming.qudongbei() xiaoming.dabaojian() laoli = Foo('老李', 90, '男') laoli.kanchai() laoli.qudongbei() laoli.dabaojian()
# ##################### 定义实现功能的类 ##################### class Person: def __init__(self, na, gen, age, fig): self.name = na self.gender = gen self.age = age self.fight =fig def grassland(self): """注释:草丛战斗,消耗200战斗力""" self.fight = self.fight - 200 def practice(self): """注释:自我修炼,增长100战斗力""" self.fight = self.fight + 200 def incest(self): """注释:多人游戏,消耗500战斗力""" self.fight = self.fight - 500 def detail(self): """注释:当前对象的详细情况""" temp = "姓名:%s ; 性别:%s ; 年龄:%s ; 战斗力:%s" % (self.name, self.gender, self.age, self.fight) print temp # ##################### 开始游戏 ##################### cang = Person('苍井井', '女', 18, 1000) # 创建苍井井角色 dong = Person('东尼木木', '男', 20, 1800) # 创建东尼木木角色 bo = Person('波多多', '女', 19, 2500) # 创建波多多角色 cang.incest() #苍井空参加一次多人游戏 dong.practice()#东尼木木自我修炼了一次 bo.grassland() #波多多参加一次草丛战斗 #输出当前所有人的详细情况 cang.detail() dong.detail() bo.detail() cang.incest() #苍井空又参加一次多人游戏 dong.incest() #东尼木木也参加了一个多人游戏 bo.practice() #波多多自我修炼了一次 #输出当前所有人的详细情况 cang.detail() dong.detail() bo.detail()
三
1.主要特点
封装: 体现在两点: 1、数据的封装(将数据封装到对象中) obj = Foo('宝宝',22) 2、封装方法和属性,将一类操作封装到一个类中 class Foo: def __init__(self,name,age): self.name = name self.age = age def show (self): print(self.name,self.age)
2.封装类属性的私有属性(就是类属性前面加__)
class Goods: # 按照打八折计算 (定义了一个私有类属性) __discount = 0.8 #变形后:_Goods__discount def __init__(self,name,price): self.name = name self.price = price def goods_price(self): return self.price * Goods.__discount apple = Goods('apple',10) print(apple.goods_price()) # print(Goods.__dict__) #类名.__dict__ print(Goods._Goods__discount)
# 封装:把你不想让人看的隐藏起来 # 数据封装:目的保护隐私 class Teacher(object): __School = 'oldboy' #类属性 def __init__(self,name,salary): self.name = name self .__salary = salary #_Teacher__salary # 老师的属性 值 #怎么把薪水隐藏起来? self.__salary=salary def foo(self): print('------',self.__salary) t=Teacher('egon',2000) print(t.__dict__) # print(t.name) print(t._Teacher__salary)#让显示出来 print(Teacher._Teacher__School) #类属性使用_类名__属性名 t.foo() #在本类内是可以正常调用的 #在本类外就必须以对象_类名__属性名调用(但是不建议你调)
3.封装类对象的私有属性
class Person: def __init__(self, height, weight, name, sex): self.__height = height # 私有属性(让你不再外面调它) # 在本类中可以调用,在类外就不可以调用了 self.__weigth = weight self.__name = name self.__sex = sex def tell_bmi(self): # 体重指数 return self.__weigth / self.__height ** 2 # 在本类中可以调用 def tell_height(self): print(self.__height) def tell_weight(self): # 告诉体重 return self.__weigth def set_weigth(self, new_weight): # 修改体重 if new_weight > 20: self.__weigth = new_weight else: raise TypeError('你也太瘦了,瘦的连斤数都(快)没了') # 如果体重小于20或者负的,就主动提示一个报错 egg = Person(1.6, 96, 'haiyan', 'female') print(egg.tell_bmi()) # egg.__height #在类外不能调用 # print(egg._Person__height) #在类外查看得这样调用 print(egg.__dict__) # 查看变形后的类型 # egg.set_weigth(-10) # print(egg.tell_weigth()) egg.set_weigth(66) # 修改体重为66 print(egg.tell_weight())
class People: def __init__(self,name,age,sex,height): self.__name = name self.__age = age self.__sex = sex self.__height = height def tell_name(self): #看人名字 print(self.name) def set_name(self,val): #修改名字 if not isinstance(val, str): raise TypeError('名字必须是字符串类型') self.__name = val def tell_info(self): print(''' ---------%s info----------- name:%s age:%s sex:%s height:%s'''%(self.__name,self.__name,self.__age,self.__sex,self.__height)) p=People('egon',21,'male','180') p.tell_info() p.set_name('haiyan') #调用修改名字的方法 p.tell_info() # print(p._People__name)#就可以看到了 对象属性的私有属性二
4.封装类方法的私有属性
class Foo: def __init__(self,height,weight): self.height = height self.weight = weight def __heightpow(self): #私有方法 return self.height * self.height def tell_bmi(self): return self.weight/self.__heightpow() egon = Foo(1.7,120) print(egon.tell_bmi()) print(Foo.__dict__) print(egon._Foo__heightpow()) #虽说是私有的,但是还是可以查看的
5.property
为什么要用property:将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
计算圆的面积和周长
from math import pi class Circle: def __init__(self,radius): self.radius = radius @property #装饰器:把一个方法当成一个属性用了 def area(self): return self.radius * self.radius* pi @property def peimeter(self): return 2*pi*self.radius c = Circle(10) print(c.area) #当成一个属性来调了,就不用加括号了 print(c.peimeter)
class Goods: __discount = 0.8 #类的私有属性 def __init__(self,name,price): self.name = name self.__price = price @property def price(self): # if hasattr(self,'__price'): return self.__price * Goods.__discount # else: # raise NameError # //返回价格打八折,通过对象加方法名字调用 @price.setter def price(self,new_price): if type(new_price) is int: self.__price = new_price # //对象+方法名字=20, 说明设置新的价格为20元, @price.deleter def price(self): del self.__price apple = Goods('apple',10) print(apple.price)//调用后价格为10 apple.price = 20 //设置新的价格为20 print(apple.price)//再次调用后价格已为20 del apple.price #//删除之后再次调用会报错 print(apple.price) apple.set_price(20) # apple._Goods__apple
1,@property 通过不加括号来调用
2,@函数名.setter 可以设置新的值 对象+方法=新值
3,函数名+deleter 删除方法 del obj.方法名
三种方式同一个方法名
继承和组合
组合:组合指的是,在一个类中以另外一个类的对象(也就是实例)作为数据属性,称为类的组合
也就是说:一个类的属性是另一个类的对象,就是组合
继承
一
1.继承是一种创建新类的方式
2.新建的类可以创建一个或多个父类,父类有称为基类或者超类
3.新建的类称为派生类或者子类
在python中类的继承分为:单继承或多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
4,3与2的区别
如果没有指定父类,python会默认继承object类,object是所有python的父类。
经典类:在python2中,class Dad: 不会继承object,这样的类叫做经典类(它叫经典类,不是因为它经典,而是因为它比较老)
新式类:在python3中,python会默认继承object类(一切皆对象)
class Dad 就相当于python3中的 class Dad(object) #新式类
而且python3中没有经典类了
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
5.继承与抽象(先抽象后继承)
抽象:抽取类似或者说比较像的部分(也就是提取一类事物的特点,范围越来越大,共性越来越少)
是从大范围到小范围的过程
继承:是基于抽象的过程,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
是从小范围到大范围的过程
6.派生:(相对论)
1.在父类的基础上产生子类,产生的子类就叫做派生类
2.父类里没有的方法,在子类中有了,这样的方法就叫做派生方法。
3.父类里有,子类也有的方法,就叫做方法的重写(就是把父类里的方法重写了)
7.注意的几个概念:
1.子类可以使用父类的所有属性和方法
2.如果子类有自己的方法,就执行自己的;如果子类没有自己的方法,就会找父类的。
3.如果子类里面没有找到,父类里也没有找到,就会报错
4.如果子类中实现了调用父类的方法
在类内:super(子类,self).方法名() supper().__init__(参数)
super().__init__(name,life_value,aggr) #super关键字 ,都不用传self了,在新式类里的
在类外:super(子类名,对象名).方法名()
def eat(self): #父类方法的重写 super().eat() print('dog is eating')
二
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:
class Animal(object): def eat(self): print("%s 吃 " % self.name) def drink(self): print("%s 喝 " % self.name) def shit(self): print("%s 拉 " % self.name) def pee(self): print("%s 撒 " % self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed = '狗' def cry(self): print('汪汪叫') # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。

那么问题又来了,多继承呢?
- 是否可以继承多个类
- 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先

- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
广度优先
class A(object): def __init__(self): print("a") class c(A): pass def __init__(self): print("c") class b(A): pass def __init__(self): print("b") class B(b): pass def __init__(self): print("B") class C(c): pass def __init__(self): print("C") class D(B,C): pass def __init__(self): print("A") d=D() //首先去D类中查找,如果D类中没有,则继续去B类中找,如果B类中么有,则继续去b类中找,如果b类中么有, 则返回去类C中找,如果没有去小c, 最后再没有则取A
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
class Animal: ''' 人和狗都是动物,所以创造一个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): pass class Person(Animal): pass egg = Person('egon',10,1000) ha2 = Dog('二愣子',50,1000) egg.eat() ha2.eat()
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.
在python3中,子类执行父类的方法也可以直接用super方法.
class A(object): def hahaha(self): print('A') class B(A): def hahaha(self): # super().hahaha()//简单写法 # super(B,self).hahaha()//复杂写法 # A.hahaha(self) print('B') a = A() b = B() b.hahaha() A super(B,b).hahaha() //在外面调用父亲的哈哈哈方法,先执行父亲的再执行自己的, b.hahaha() //A B
接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Alipay() pay(p,200)
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) self.money = money #派生出来的一个属性 def attack(self,enemy): #人的派生方法 #enemy.life_value = enemy.life_value - self.aggr enemy.life_value -= self.aggr class Dog(Animal): #子类 派生类 def __init__(self,name,breed, life_value,aggr): # Animal.__init__(self,name,breed, life_value,aggr)#让子类执行父类的方法 就是父类名.方法名(参数),连self都得传 super().__init__(name,life_value,aggr) #super关键字 ,都不用传self了,在新式类里的 # 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) # egg = Person('egon',500,1000,50) # print(egg.aggr) # print(ha2.aggr) # egg.eat() # print(egg.life_value) # # egg.eat() # print(egg.life_value) # # ha2.eat() # print(ha2.life_value) # # print(egg.life_value) # ha2.bite(egg) # print(egg.life_value) # ha2 = Dog('牛头梗','旺财',20000,100) print(ha2.life_value) ha2.eat() #如果父类有的方法子类里面也有,那么就叫做方法的重写,就不执行父类的了,去执行子类了 print(ha2.life_value) super(Dog,ha2).eat() #生掉父类的方法,但是不推荐这样用 print(ha2.life_value) #在继承中,如果子类有的方法就执行子类的 # 如果子类没有的方法就执行父类的
几个名词
抽象:抽象即抽取类似或者说比较像的部分。是一个从具题到抽象的过程。
继承:子类继承了父类的方法和属性
派生:子类在父类方法和属性的基础上产生了新的方法和属性
多态
多态指的是一类事物有多种形态(比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同)
peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
class Animall(object): def __init__(self,name): self.name=name def talk(self): pass def annimao(x): x.talk() class Dog(Animall): def talk(self): print("%s: 汪汪汪 "%self.name) class Cat(Animall): def talk(self): print("%s: 喵喵喵 "%self.name) d1=Dog("文学") # d1.talk() d2=Cat("争强") # d2.talk() d1.annimao() Dog.annimao(d2) //本来需要写两个方法去叫,现在写一个就可以, 一个端口两种实现
总结: 多态 一种接口 多种实现

浙公网安备 33010602011771号