面向对象三大特征:
封装根据职责将属性和方法封装到一个抽象的类中
继承实现代码的重用,相同的代码不需要重复的编写
多态不同的对象调用相同的方式,产生不同的执行结果,增加代码的灵活度
一、封装
1. 封装是面向对象编程的一大特点
2. 面向对象编程的第一步--将属性和方法封装到一个抽象类中
3. 外界使用类创建对象,然后让对象调用方法
4. 对象方法的细节都被封装在类的内部
1.1、小明爱跑步(案例)
需求
1.小明体重75公斤
2.小明每次跑步会减肥0.5公斤
3.小明每次吃东西体重会增加1公斤
class Person:
#在对象的方法内部可以直接访问对象的属性
def __init__(self,new_name,new_weight):
self.name=new_name
self.weight=new_weight
def __str__(self):
return "我的名字叫%s,体重是%.2f公斤"%(self.name,self.weight)
def run(self):
self.weight=self.weight-0.5
print("%s跑完步后的体重是%.2f"%(self.name,self.weight))
def eat(self):
self.weight=self.weight+1
print("%s吃完东西后的体重是%.2f"%(self.name,self.weight))
xiaoming=Person("小明",75)
print(xiaoming)
xiaoming.run()
xiaoming.eat()
#结果
我的名字叫小明,体重是75.00公斤
小明跑完步后的体重是74.50
小明吃完东西后的体重是75.50
1.2、小明和小美都爱跑步
1.小明体重75公斤
2.小美体重45公斤
3.每次跑步减少0.5公斤
4.每次吃东西增加1公斤
提示:
在对象的方法内部,可以直接访问对象的属性
同一个类创建的多个对象之间,属性互不干扰
class Person:
def __init__(self,new_name,new_weight):
self.name=new_name
self.weight=new_weight
def __str__(self):
return "我的名字叫%s,体重是%.2f公斤"%(self.name,self.weight)
def run(self):
self.weight=self.weight-0.5
print("%s跑完步后的体重是%.2f"%(self.name,self.weight))
def eat(self):
self.weight=self.weight+1
print("%s吃完东西后的体重是%.2f"%(self.name,self.weight))
xiaoming=Person("小明",75)
xiaomei=Person("小美",45)
print(xiaoming)
print(xiaomei)
xiaoming.run()
xiaoming.eat()
xiaomei.run()
xiaomei.eat()
#结果
我的名字叫小明,体重是75.00公斤
我的名字叫小美,体重是45.00公斤
小明跑完步后的体重是74.50
小明吃完东西后的体重是75.50
小美跑完步后的体重是44.50
小美吃完东西后的体重是45.50
1.3、摆放家具
需求
1.房子(House)有户型、总面积和家具名称列表
新房子没有任何家具
2.家具(HouseItem)有名字和占地面积,其中
席梦思(bed)占地面积4平米
衣柜(chest)占地面积2平米
餐桌(table)占地面积1.5平米
3.将以上三件家具添加到房子中
4.打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
添加家具
需求:
1.判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具
2.将家具的名称追加到家具列表中
3.用房子的剩余面积-家具面积
class HouseItem:
def __init__(self,name,area):
self.name=name
self.area=area
def __str__(self):
return "[%s] 占地面积是%.2f"%(self.name,self.area)
class House:
def __init__(self,house_type,area):
self.house_type=house_type
self.area=area
#剩余面积
self.free_are=area
self.item_list=[]
def __str__(self):
return "户名:%s\n面积:%.2f\n剩余面积:%.2f\n家具:%s"%(self.house_type,self.area,self.free_are,self.item_list)
def add_item(self,item):
print("要添加的家具:%s"%item)
#判断家具面积
if item.area>self.free_are:
print("[%s]面积太大,已经放不下"%item.name)
return
#将家具的名称添加到列表中
self.item_list.append(item.name)
#计算剩余面积
self.free_are-=item.area
#创建家具
bed=HouseItem("席梦思",4)
closet=HouseItem("衣柜",2)
table=HouseItem("餐桌",1.5)
print(bed)
print(closet)
print(table)
#创建房子对象
home=House("两室一厅",120)
home.add_item(bed)
home.add_item(table)
home.add_item(closet)
print(home)
#结果
[席梦思] 占地面积是4.00
[衣柜] 占地面积是2.00
[餐桌] 占地面积是1.50
要添加的家具:[席梦思] 占地面积是4.00
要添加的家具:[餐桌] 占地面积是1.50
要添加的家具:[衣柜] 占地面积是2.00
户名:两室一厅
面积:120.00
剩余面积:112.50
家具:['席梦思', '餐桌', '衣柜']
1.4、士兵突击
需求
1.士兵许三多有一把AK47
2.士兵可以开火
3.枪能发射子弹
4.枪装填子弹
开发士兵类:
假设:每一个士兵都没有枪:定义没有初始值的属性
-
None关键字表示什么都没有
-
表示一个空对象,没有方法和属性,是一个特殊的常量
-
可以将None赋值给任何一个变量
fire方法
-
判断是否有枪,没有没法冲锋
-
喊一声口号
-
装填子弹
-
射击
class Gun:
def __init__(self,model):
self.model=model
self.bullet_count=0
def shoot(self):
if self.bullet_count<=0:
print("[%s]没有子弹"%self.model)
return
self.bullet_count-=1
print("[%s]突突突....还有[%d]颗子弹"%(self.model,self.bullet_count))
def add_bulet(self,count):
self.bullet_count+=count
print("[%s]装填%d颗子弹"%(self.model,count))
class Soldier:
def __init__(self,name):
self.name=name
self.gun=None
def fire(self):
#判断是否有枪
if self.gun==None:
print("[%s]没有枪,不能开火"%self.name)
return
#高喊口号
print("冲啊...[%s]"%self.name)
#让枪装填子弹
self.gun.add_bulet(50)
#让枪发射子弹
self.gun.shoot()
#创建枪对象
ak47=Gun("AK47")
#创建士兵对象
xusanduo=Soldier("许三多")
xusanduo.gun=ak47
xusanduo.fire()
#结果
[AK47]没有子弹
冲啊...[许三多]
[AK47]装填50颗子弹
[AK47]突突突....还有[49]颗子弹
1.5:身份运算符
身份运算符用于比较两个对象的内存地址是否一致——是否是对同一个对象的引用
- 在Python中针对None比较时,建议使用is判断
运算符 | 描述 | 实例 |
---|---|---|
is | is是判断两个标识符是不是引用同一个对象 | x is y,类似id(x)==id(y) |
not is | not is是判断两个标识符是不是引用不同对象 | x is not y,类似id(x)!=id(y) |
is与==区别
is用于判断两个变量引用对象是否为同一个
==用于判断引用变量的值是否相等
私有属性和私有方法
1.应用场景及定义方法
应用场景
- 在实际开发中,对象的某些属性或方法可能只希望在对象的内部使用,而不希望在外部被访问到
- 私有属性就是对象不希望公开的属性
- 私有方法就是对象不希望公开的方法
定义方式
在定时属性或方法时,在属性名或者方法名前增加两个下划线
class Women:
def __init__(self,name):
self.name=name
self.age=18
def secret(self):
print("[%s]的年龄是:%d"%(self.name,self.age))
xiaomei=Women("小美")
print(xiaomei.age)
xiaomei.secret()
#结果
18
[小美]的年龄是:18
#把年龄变为私有属性
class Women:
def __init__(self,name):
self.name=name
self.__age=18
def __secret(self):
#在对象的方法内部,可以访问对象的私有属性
print("[%s]的年龄是:%d"%(self.name,self.__age))
xiaomei=Women("小美")
#私有属性在外部不能被直接访问,如果访问则会报错
print(xiaomei.age)
#结果
AttributeError: 'Women' object has no attribute 'age'
#私有方法,同样不能在外界使用
xiaomei.__secret()
#结果
AttributeError: 'Women' object has no attribute '___secret'
伪私有属性和私有方法
提示:在日常开发中,不要使用这种方法,访问对象的私有属性和私有方法
Python中,并没有真正意义的私有
再给属性、方法命名时,实际是对名称做了一些特殊的处理,使得外界无法访问到
处理方式:在名称前面加上__类名=>_类名__名称
class Women:
def __init__(self,name):
self.name=name
self.__age=18
def __secret(self):
#在对象的方法内部,可以访问对象的私有属性
print("[%s]的年龄是:%d"%(self.name,self.__age))
xiaomei=Women("小美")
print(xiaomei._Women__age)
xiaomei._Women__secret()
#结果
18
[小美]的年龄是:18
二、继承
目标:
- 单继承
- 多继承
继承的概念:子类拥有父类 的所有方法和属性
继承的语法
class 类名(父类名)
- 子类继承自父类,可以直接使用父类中已经封装好的方法,不需要再次开发
- 子类中应该根据职责,封装子类特有的属性和方法
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Dog(Animal):
def bark(self):
print("汪汪")
#创建一个对象
wangcai=Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
#结果
吃
喝
跑
睡
汪汪
专业术语
- Dog类是Animal类的子类,Anlmal类是Dog类的父类,Dog类从Animal类继承
- Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生
继承的传递性
- C类从B类继承,B类又从A类继承,那么C类就具有B类和A类的所有属性和方法
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Dog(Animal):
def bark(self):
print("汪汪")
class XiaoTianQuan(Dog):
def fly(self):
print("飞啊")
#创建一个对象
wangcai=XiaoTianQuan()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
wangcai.fly()
#结果
吃
喝
跑
睡
汪汪
飞啊
方法的重新
- 子类拥有父类的所有方法和属性
- 子类继承自父类,可以直接使用父类中已经封装好的方法,不需要再次开发
应用场景
当父亲的方法实现不能满足子类需求时,可以对方法进行重写(override)
重写父类有两种情况:
1、覆盖父类的方法
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Dog(Animal):
def bark(self):
print("汪汪")
class XiaoTianQuan(Dog):
def fly(self):
print("飞啊")
#如果子类中,重写了父类的方法
#在使用子类时,会调用子类中重写的方法
def bark(self):
print("嗷呜~")
#创建一个对象
wangcai=XiaoTianQuan()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
wangcai.fly()
#结果
吃
喝
跑
睡
嗷呜~
飞啊
2、对父类方法进行扩展
-
如果在开发中子类的方法实现中包含父类的方法
-
父类原本封装的方法实现时子类方法的一部分,就可以使用扩展的方式
在子类中重写父类的方法 在需要的位置使用super(),父类方法来调用父类方法的执行 代码其他的位置针对子类的需求,编写子类特有的代码实现
关于super
- 在Python中super是一个特殊的类
- super()就是使用super类创建出来的对象
- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Dog(Animal):
def bark(self):
print("汪汪")
class XiaoTianQuan(Dog):
def fly(self):
print("飞啊")
# #如果子类中,重写了父类的方法
# #在使用子类时,会调用子类中重写的方法
# def bark(self):
# print("嗷呜~")
def bark(self):
#针对子类的特有需求,编写代码
print("嗷呜~")
#使用super()调用原本在父类中封装的方法
super().bark()
#创建一个对象
wangcai=XiaoTianQuan()
wangcai.bark()
#结果
嗷呜~
汪汪
父类的私有属性和私有方法
1.子类对象不能在自己的方法内部,直接访问父类的私有属性和私有方法
2.子类对象可以通过父类的功有方法间接访问到私有属性或私有方法
- 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能访问
- 私有属性、方法通常用于做一些内部的事情
class A:
def __init__(self):
self.num1=100
self.__num2=200
def __test(self):
print("这是A类中的私有方法")
def test(self):
print("公有数字num1:[%d] 私有数字num2:[%d]"%(self.num1,self.__num2))
self.__test()
class B(A):
def demo(self):
#结果
公有数字num1:[100] 私有数字num2:[200]
这是A类中的私有方法
- B的对象不能直接访问__num2属性
- B的对象不能在demo方法内访问__num2属性
- B的对象可以在demo方法内,调用父类的test方法
- B的对象不可以在demo方法内,调用父类的__test方法
- 父类的test方法内部,能够访问__num2属性和__test方法
多继承
概念
- 子类可以拥有多个父类,并且具有所有父类的属性和方法
- 例如:孩子会继承自己父亲和母亲的特性
语法
class 子类名(父类名1,父类名2…)
class A:
def test(self):
print("test方法")
class B:
def demo(self):
print("demo方法")
class C(A,B):
pass
#创建子类对象
c=C()
c.test()
c.demo()
#结果
test方法
demo方法
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
(1)
class A:
def test(self):
print("A test方法")
def demo(self):
print("A demo方法")
class B:
def demo(self):
print("B demo方法")
def test(self):
print("B test方法")
class C(A,B):
pass
#创建子类对象
c=C()
c.test()
c.demo()
#结果
A test方法
A demo方法
(2)
class A:
def test(self):
print("A test方法")
def demo(self):
print("A demo方法")
class B:
def demo(self):
print("B demo方法")
def test(self):
print("B test方法")
class C(B,A):
pass
#创建子类对象
c=C()
c.test()
c.demo()
#结果
B test方法
B demo方法
Python中的MRO–方法搜索
- Python中针对类提供了一个内置属性__mro__可以查看方法的搜索路径
- MRO是method resolution order的缩写,主要用于在多继承时判断方法、属性的调用路径
class A:
def test(self):
print("A test方法")
def demo(self):
print("A demo方法")
class B:
def demo(self):
print("B demo方法")
def test(self):
print("B test方法")
class C(B,A):
pass
#创建子类对象
c=C()
print(C.__mro__)
#结果
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
- 在搜索方法时,是按照__mro__的输出结果从左至右的顺序查找的 如果在当前类中,找到方法就直接执行,不在搜索
- 如果没有找到,就查找下一个类中是否有对应的方法,如果找到就直接执行,不再执行
- 如果找到最后一个类,还没有找到方法,程序报错
三、多态
案例
1.在Dog类中封装方法game
普通狗只是简单的玩耍
2.定义XiaoTianQuan继承自Dog,并且重写game方法
哮天犬需要在天上玩耍
3.定义Person类,并且封装一个和狗玩耍的方法
在方法内部,直接让狗对象调用game方法
class Dog(object):
def __init__(self,name):
self.name=name
def game(self):
print("%s跑来跑去"%self.name)
class XiaoTianQuan(Dog):
def game(self):
print("%s飞来飞去"%self.name)
class Person(object):
def __init__(self,name):
self.name=name
def game_with_dog(self,dog):
print("%s和%s玩耍"%(self.name,dog.name))
dog.game()
wangcai=Dog("旺财")
xiaohong=Person("小红")
xiaohong.game_with_dog(wangcai)
wangcai=XiaoTianQuan("哮天犬")
xiaohong.game_with_dog(wangcai)
#结果
小红和旺财玩耍
旺财跑来跑去
小红和哮天犬玩耍
哮天犬飞来飞去
-
Person类中只需要让狗对象调用game方法,而不关心具体是什么狗
game方法实在Dog父类中定义的
-
在程序执行时,传入不同的狗对象实参,就会产生不同的执行结果