面向对象封装案例
01. 小明爱跑步
- 需求
- 体重75公斤
- 每次跑步减0.5公斤
- 吃东西体重增加1公斤
class Person:
def __init__(self,name,weight):
# self.属性 = 形参
self.name = name
self.weight = weight
def __str__(self):
return "我的名字是%s 体重是%.2f 公斤" % (self.name,self.weight)
def run(self):
print("%s 爱跑步 , 跑步锻炼身体" % self.name)
self.weight -= 0.5
def eat(self):
print("%s 爱吃饭 , 吃饭增长体重" % self.name)
self.weight += 1
xiaoming = Person("小明",75.0)
xiaoming.run()
xiaoming.eat()
print(xiaoming)
xiaomei = Person("小美",45.0)
xiaomei.run()
xiaomei.eat()
print(xiaomei)
# 多个对象之间互不影响
小明 爱跑步 , 跑步锻炼身体
小明 爱吃饭 , 吃饭增长体重
我的名字是小明 体重是75.50 公斤
小美 爱跑步 , 跑步锻炼身体
小美 爱吃饭 , 吃饭增长体重
我的名字是小美 体重是45.50 公斤
02.摆放家具
- 需求
- 房子有户型,总面积和家具名称列表
- 新房子无家具
- 家具:名字和占地面积
- 席梦思:4
- 衣柜:2
- 餐桌:1.5
- 将家具添加到房子
- 打印房子输出:户型,总面积,剩余面积,家具名称列表
- 剩余面积
- 创建对象时定义
- 调用add_item方法添加家具
- 先开发哪个类?
家具类:家具类简单,房子要使用到家具,在开发中被使用的类先开发
class HouseItem:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return "%s 占地 %.2f" % (self.name, self.area)
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
席梦思 占地 4.00
衣柜 占地 2.00
餐桌 占地 1.50
定义房子类
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_area = area
self.item_list = []
def __str__(self):
return ("户型:%s\\n总面积: %.2f\\n剩余面积:%.2f\\n家具: %s"
% (self.house_type, self.area,
self.free_area, self.item_list))
# python能自动将括号里的代码连接到一起
def add_item(self,item):
print("要添加%s" % item)
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
my_house = House("两室一厅", 60)
my_house.add_item(bed)
my_house.add_item(chest)
my_house.add_item(table)
席梦思 占地 4.00
衣柜 占地 2.00
餐桌 占地 1.50
要添加席梦思 占地 4.00
要添加衣柜 占地 2.00
要添加餐桌 占地 1.50
完成摆放家具:
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_area = area
self.item_list = []
def __str__(self):
return ("户型:%s\\n总面积: %.2f\\n剩余面积:%.2f\\n家具: %s"
% (self.house_type, self.area,
self.free_area, self.item_list))
# python能自动将括号里的代码连接到一起
def add_item(self,item):
print("要添加%s" % item)
if item.area > self.free_area:
print("%s面积太大" % item.area)
return
self.item_list.append(item.name)
self.free_area -= item.area
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
my_house = House("两室一厅", 60)
my_house.add_item(bed)
my_house.add_item(chest)
my_house.add_item(table)
print(my_house)
席梦思 占地 4.00
衣柜 占地 2.00
餐桌 占地 1.50
要添加席梦思 占地 4.00
要添加衣柜 占地 2.00
要添加餐桌 占地 1.50
户型:两室一厅
总面积: 60.00
剩余面积:52.50
家具: ['席梦思', '衣柜', '餐桌']
03.士兵突击
一个对象的属性可以是另一个类创建的对象
- 需求:
- 士兵 许三多 有一把ak47
- 士兵可以开火
- 枪可以发射子弹
- 枪装填子弹
枪类:
class Gun:
def __init__(self, model):
self.model = model
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
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))
ak47 = Gun("ak47")
ak47.add_bullet(50)
ak47.shoot()
ak47突突突49
士兵类:
class Gun:
def __init__(self, model):
self.model = model
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
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))
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_bullet(50)
self.gun.shoot()
ak47 = Gun("ak47")
xusanduo = Soldier("许三多")
xusanduo.gun = ak47
xusanduo.fire()
冲啊...许三多
ak47突突突49
身份运算符(补充)
用于比较两个对象的内存地址是否相同—是否为对同一个对象的引用
- 在python中针对None的比较建议使用is
- ==用于判断两个引用变量的值是否相等
私有属性和私有方法
01.应用场景及定义方法
应用场景
- 对象的某些属性和方法不需要对外界公开,只在内部使用
定义方式
- 在属性名或方法名前加两个下划线
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def secret(self):
# 在对象的方法内部可以访问私有属性
print("%s的年龄是%d" % (self.name, self.__age))
xiaofan = Women("小芳")
# print(xiaofan.age)
xiaofan.secert()
小芳的年龄是18
02.伪私有属性和私有方法(了解)
python中不存在真正的私有属性和私有方法
- 在给出的属性、方法命名时对名称做特殊处理导致外界访问不到
- 处理方式:在名称前加上_类名 ⇒ _类名__名称
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def secret(self):
# 在对象的方法内部可以访问私有属性
print("%s的年龄是%d" % (self.name, self.__age))
xiaofan = Women("小芳")
print(xiaofan._Women__age)
xiaofan.secret()
- 子类对象不能在自己的方法内部直接访问父类的私有方法和属性
- 子类可以通过父类的公有方法访问到私有方法和私有属性
继承
面向对象三大特性:
- 封装
- 继承 子类拥有父类所以的方法
- 多态
- 语法:
class Animal:
def eat(self):
print("吃")
**class dog(Animal):**# 继承
def bark(self):
print("叫")
dog = dog()
dog.bark()
dog.eat()
- 术语: 子类,父类,继承
派生类,基类,派生 - 继承的传递性
- c继承b,b继承a
- c类拥有b和a所以属性和方法
- 方法的重写
- 父类的方法不能满足子类的需求,就需要子类对方法进行重写
- 覆盖父类的方法:在子类中重新编写父类 的方法实现(定义一个同名方法)
- 对父类方法进行扩展:子类方法实现存在父类方法。在子类中重写父类方法,在需要的位置通过 super().父类方法 实现对父类方法的执行
class Animal:
def eat(self):
print("吃")
class dog(Animal):# 继承
def bark(self):
print("叫")
def eat(self):
print("吃骨头")
super().eat()
dog = dog()
dog.bark()
dog.eat()
叫
吃骨头
吃
- 另一种调用父类方法:父类名.方法(self),不推荐这种方式(了解)
多继承
概念:子类可以拥有多个父类,拥有所有父类的属性和方法
语法:class 子类名(父类名1,父类名2)
- 开发时如果不同的父类中会有同名的方法,应该避免多继承
- python中的MRO—方法搜索顺序:
- python中提供内置对象__mro__可以查看方法搜索顺序
- mro主要用于多继承时判断方法、属性的调用路径
- 输出时是按照__mro__的输出结果从左向右的顺序查找
- 在当前类中找到方法就不再执行
- 如果在Object类中也没有找到就会报错
当前类→父类1→父类2→object类
新式类和旧式类
object是python为所有对象提供的基类,提供一些内置方法和属性,可以使用dir方法函数查看
- 新式类:以object为基类的类
- 旧式类:不以object为基类的类
class A(object):
pass
class B:
pass
a = A()
b = B()
print(dir(a))
print(dir(b))
# 新版本python中默认为新式类
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
多态
特性:不同子类对象调用相同的方法,产生不同执行结果
- 可以增加代码灵活度
- 以继承和重写父类方法 为前提
- 不会影响类的内部设计
案例演练
需求
- 在dog类中封装game方法
- 定义xiaotianquan继承dog类,重写game方法
- 定义person类,封装和狗玩的方法
class Dog():
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():
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s和%s快乐的玩耍" % (self.name, dog.name))
dog.game()
wangcai = Dog("旺财")
huahua = xiaotianquan("飞天花花")
xiaoming = person("小明")
xiaoming.game_with_dog(wangcai)
xiaoming.game_with_dog(huahua)
小明和旺财快乐的玩耍
旺财蹦蹦跳跳的玩耍
小明和飞天花花快乐的玩耍
飞天花花飞到天上去玩
类属性
01. 类的结构
- 使用类名()创建对象,分为两部:
- 在内存中分配空间
- 调用初始化方法__init__为对象初始化
- 对象创建后,内存中就存在实例
- 对象叫做类的实例
- 创建对象叫做实例化
- 对象的属性为实例属性
- 对象调用的方法叫做实例方法
- 多个对象的方法在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
02. 类是一个特殊的对象
- class AAA:定义的类属于类对象
- obj1 = AAA()属于实例对象
- 类对象在内存中只有一份,一个类可以创建出多个实例对象
- 除封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
- 类属性
- 类方法
- 通过 类名.的方法访问类的属性和方法
03. 类属性和实例属性
概念:
- 类属性为类对象中定义的属性
- 记录类的相关特征
- 类属性不会用于记录具体对象特征
示例需求:
- 定义一个工具类
- 每件工具都有自己的name
- 需求——知道使用这个类,创建了多少个工具对象?
class Tool(object):
count = 0
def __init__(self,name):
self.name = name
Tool.count +=1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
print(Tool.count)
3
属性获取机制(了解):
- 在python中获取属性存在向上查找机制
- 首先在对象内部查找对象属性
- 没有找到就会向上寻找类属性
class Tool(object):
count = 0
def __init__(self,name):
self.name = name
Tool.count +=1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
print(tool3.count)
# 不建议使用,因为会给对象添加count属性
3
类方法
类方法的创建:
- 语法:
@classmethod
def 类方法名(cls):
pass
- cls告诉解释器这是一个类方法
- 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
- 类似于self
- 使用其他名称也可(不推荐)
- 在方法内可以通过cls.访问类属性,也可以调用其他类方法
实力需求:
- 定义一个工具类
- 每件工具都有自己的name
- 需求——在类中封装一个show_tool_count的类方法,输出使用当前这个类,创建的对象个数
class Tool(object):
count = 0
@classmethod
def show_tool_count(cls):
print("工具对象的数量 %d" % cls.count)
def __init__(self,name):
self.name = name
Tool.count +=1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
Tool.show_tool_count()
工具对象的数量 3
静态方法
- 在开发中,需要封装一个方法:
- 不需要访问实例属性或者实例方法
- 也不需要访问类属性和类方法
- 这个时候可以封装成静态方法
- 通过 类名. 调用
- 语法
@staticmethod def 静态方法名(): pass
简单的示例:
class Dog(object): @staticmethod def run(): print("小狗要跑") Dog.run() # 通过类名访问不需要实例化 小狗要跑
方法综合案例
需求:
- 设计一个Game类
- 属性:
- 定义一个类属性 top_score记录游戏的历史最高得分
- 定义一个示例属性 player_name记录当前游戏的玩家姓名
- 方法:
- 静态方法 show_help显示游戏的帮助信息
- 类方法 show_top_score 显示历史最高分
- 实例方法 start_game开始当前玩家的游戏
- 主程序步骤
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
class Game(object):
top_score = 0
def __init__(self, player_name):
self.player_name = player_name
@staticmethod
def show_help():
print("帮助信息:")
@classmethod
def show_top_score(cls):
print("历史记录%d" % cls.top_score)
def start_game(self):
print("%s开始游戏啦" % self.player_name)
Game.show_help()
Game.show_top_score()
game = Game("小明")
game.start_game()
帮助信息:
历史记录0
小明开始游戏啦
单例
01. 单例设计模式
设计模式:
- 设计模式是前人工作的总结,针对某一个特定问题的成熟的解方案
- 为了可重用代码,让代码更容易理解
单例设计模式:
- 目的——让 类 创建的对象,在系统中只有一个实例
- 每一次执行 类名() 返回的对象,内存地址相同
**new 方法:**
- 使用类名()创建对象时,会首先调用__new__()方法为对象分配空间
- new()方法是内置的静态方法
- 为对象分配空间
- 返回对象引用
- 解释器获得对象引用后,将引用作为第一个参数,传递给__init__方法
- 重写__new__()方法一定要return super().new(cls)
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
print("创建对象,分配空间")
return super().__new__(cls)
def __init__(self):
print("播放器初始化")
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1)
print(player2)
创建对象,分配空间
播放器初始化
创建对象,分配空间
播放器初始化
<__main__.MusicPlayer object at 0x0000028CFCFB3E50>
<__main__.MusicPlayer object at 0x0000028CFCFB3E20>
- 单例设计模式代码实现
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 判断类属性是否为空对象
if cls.instance is None:
# 调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
return cls.instance
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1)
print(player2)
<__main__.MusicPlayer object at 0x0000024C8E613EE0>
<__main__.MusicPlayer object at 0x0000024C8E613EE0>
扩展:
- 每次创建对象时,python会自动调用两个方法:
- __new__分配空间
- __init__对象初始化
- 创建对象时还会调用初始化方法
- 需求:让初始化方法只执行一次
- 解决方法:
- 定义类属性init_flag标记是否执行初始化动作
- 在__init__中判断init_flag
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化方法
init_flag = False
def __new__(cls, *args, **kwargs):
# 判断类属性是否为空对象
if cls.instance is None:
# 调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
# 判断是否执行初始化动作
if MusicPlayer.init_flag:
return
# 执行初始化动作
print("初始化播放器")
# 修改标记值
MusicPlayer.init_flag = True
player1 = MusicPlayer()
player2 = MusicPlayer()
print(player1)
print(player2)
初始化播放器
<__main__.MusicPlayer object at 0x0000022EE6713E80>
<__main__.MusicPlayer object at 0x0000022EE6713E80>