本笔记参考视频为:https://www.bilibili.com/video/BV1ex411x7Em?p=101
,有兴趣的可以直接移步B站
面向对象编程——Object Oriented Orogramming
简写OOP
所有步骤
,从头到尾逐步实现类和对象是面向对象编程的两个核心概念
在程序开发中,应该现有类,再有对象
在使用面向对象开发前,应该首先分析需求,确定以下程序中需要包含那些类
以植物大战僵尸游戏为例分析类的设计如下图
在程序开发中,要设计一个类,通常需要满足以下三个要素
名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类
在ipython
中快速确认某个东西是对象的方法:
1.在标识符/数据后输一个.
,然后按下TAB
键,ipython会提示该对象能够调用的方法列表(只能看到一些常用的方法列表)
2.使用内置函数dir
传入标识符/数据,可以查看对象内的所有属性及方法
提示:__方法名__
格式的方法是Python提供的内置方法/属性,以下是一些常用的内置方法/属性
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __new__ | 方法 | 创建对象时,会被自动调用 |
02 | __init__ | 方法 | 对象被初始化时,会被自动调用 |
03 | __del__ | 方法 | 对象从内存被销毁前,会被自动调用 |
04 | __str__ | 方法 | 返回对象的描述信息,print函数输出使用 |
语法格式:
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
self
语法格式:
对象变量 = 类名()
需求:
小猫爱吃鱼,小猫要喝水
分析:
# 创建猫类
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建一个对象
Tom = Cat()
Tom.drink()
Tom.eat()
引用概念的强调
在面向对象开发中,引用的概念是同样适用的!
tom
变量中仍然记录的是对象在内存中的地址tom
变量引用了新建的猫对象print
输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址提示:在计算机中,通常使用十六进制表示内存地址
%d
可以以10进制输出数字%x
可以以16进制输出数字
.
设置一个属性即可以上一节中的代码为例,在创建了小猫对象Tom = Cat()
之后,想要从类外部给Tom增加一个姓名属性,可以直接在执行代码出输入Tom.name = "Tom"
即可
想要在类内部调用某个属性,可以直接self.属性名
哪一个对象调用的方法,self就是哪一个对象的引用。self类似C++里面的this指针
self
就表示当前调用方法的对象自己self
函数self.
访问对象的属性self.
调用其他的对象方法示例:
# 创建猫类
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
def drink(self):
print("%s 要喝水" % self.name)
# 创建一个对象
Tom = Cat()
Tom.name = "Tom"
Tom.drink()
Tom.eat()
执行上述程序解释器会报错,因为在执行eat()
和drink()
方法的时候还没有name
属性
类名()
创建对象时,会自动执行以下操作:
init
)__init__
方法,__init__
是对象的内置方法
__init__
方法是专门用来定义一个类具有那些属性的方法
__init__
方法内部使用self.属性名=属性的初始值
就可以定义属性Cat
类创建的对象,都会拥有该属性示例:
# 创建猫类
class Cat:
def __init__(self):
self.name = "Jerry"
def eat(self):
print("%s 爱吃鱼" % self.name)
def drink(self):
print("%s 要喝水" % self.name)
# 创建一个对象
Tom = Cat()
Tom.drink()
Tom.eat()
__init__
方法进行改造
__init__
方法的参数self.属性=形参
接受外部传递的参数类名(属性1, 属性2...)
调用示例:
# 创建猫类
class Cat:
def __init__(self, name_):
self.name = name_
def eat(self):
print("%s 爱吃鱼" % self.name)
def drink(self):
print("%s 要喝水" % self.name)
# 创建一个对象
Tom = Cat("汤姆")
Tom.drink()
Tom.eat()
lazy_cat = Cat("大懒猫")
lazy_cat.drink()
lazy_cat.eat()
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __del__ |
方法 | 对象被从内存中销毁前,会被自动调用 |
02 | __str__ |
返回对象的描述信息,print函数输出使用 |
类名()
创建对象时,为对象分配空间后,自动调用__init__
方法__del__
方法__init__
改造初始化方法,可以让创建对象更加灵活__del__
如果希望在对象销毁前,再做一些事情,可以考虑以下该方法类名()
创建,生命周期开始__del__
方法一旦被调用,生命周期结束示例:
# 创建猫类
class Cat:
def __init__(self, name):
self.name_ = name
print("%s 来了" % self.name_)
# 定义析构函数
def __del__(self):
print("%s 走了" % self.name_)
# 创建一个对象
tom = Cat("Tom")
print(tom.name_)
print("-" * 20)
结果:
分析:
tom是一个全局变量,只有在整个程序结束之后才会被销毁,因此会先打印----------,在执行析构函数中的语句
Python中有一个关键字
del
,可以直接销毁变量,若在打印-------之前销毁tom变量,则会先执行析构函数中的语句,代码如下:
# 创建猫类
class Cat:
def __init__(self, name):
self.name_ = name
print("%s 来了" % self.name_)
# 定义析构函数
def __del__(self):
print("%s 走了" % self.name_)
# 创建一个对象
tom = Cat("Tom")
print(tom.name_)
del(tom)
print("-" * 20)
print
输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)[见3.2.3]print
输出对象变量时能够打印自定义的内容,就可以利用__str__
这个内置方法了注意:
__str__
方法必须返回一个字符串
# 创建猫类
class Cat:
def __init__(self, name):
self.name_ = name
print("%s 来了" % self.name_)
# 定义析构函数
def __del__(self):
print("%s 走了" % self.name_)
def __str__(self):
# 必须返回一个字符串
return "我是小猫 %s" % self.name_
# 创建一个对象
tom = Cat("Tom")
print(tom)
如上例,在使用
tom
时,输出了__str__
中返回的内容
需求
75.0
公斤class Person:
def __init__(self, name, weight) -> None:
self.name_ = name
self.weight_ = weight
def __str__(self) -> str:
return "我的名字是 %s,我的体重是 %.2f 公斤" % (self.name_, self.weight_)
def run(self):
print("跑步好,跑步减肥")
self.weight_ -= 0.5
def eat(self):
print("没吃饱哪有力气减肥")
self.weight_ += 1
xiaoming = Person("小明", 75.0)
xiaoming.eat()
xiaoming.run()
print(xiaoming)
需求:
示例:
class Person:
def __init__(self, name, weight) -> None:
self.name_ = name
self.weight_ = weight
def __str__(self) -> str:
return "我的名字是 %s,我的体重是 %.2f 公斤" % (self.name_, self.weight_)
def run(self):
print("跑步好,跑步减肥")
self.weight_ -= 0.5
def eat(self):
print("没吃饱哪有力气减肥")
self.weight_ += 1
xiaoming = Person("小明", 75.0)
xiaoming.eat()
xiaoming.run()
print(xiaoming)
xiaomei = Person("小美", 45.0)
xiaomei.eat()
xiaomei.run()
print(xiaomei)
需求
class HouseItem:
def __init__(self, name, area) -> None:
self.name = name
self.area = area
def __str__(self) -> str:
return "[%s] 占地 %.2f " % (self.name, self.area)
class House:
def __init__(self,house_type, area) -> None:
self.house_type = house_type
self.area = area
# 剩余面积
self.free_area = area
# 家具列表
self.item_list = []
def __str__(self) -> str:
return "户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s" % (self.house_type, self.area, self.free_area,self.item_list)
def add_item(self, item):
print("要添加的家具:%s" % item)
# 判断能否装下
if item.area > self.free_area:
print("家具太大,房子装不下")
return
# 更新家具列表
self.item_list.append(item.name)
# 更新剩余面积
self.free_area -= item.area
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌",1.5)
my_home = House("两室一厅",60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
需求:
定义没有初始值的属性
在定义属性时,如果不知道设置什么初始值,可以设置为None
None
关键字表示什么都没有None
赋值给任何一个变量fire
方法需求
示例:
class Gun:
def __init__(self, type) -> None:
self.type = type
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
# 判断有无子弹
if self.bullet_count <= 0:
print("没子弹啦!!!")
return
# 发射子弹
self.bullet_count -= 1
# 提示发射信息
print("[%s] biubiubiu~! [%d]" % (self.type, self.bullet_count))
class Soldier:
def __init__(self, name) -> None:
self.name = name
# 新兵一开始没有枪
self.gun = None
def fire(self):
# 判断是否有枪
if self.gun == None:
print("[%s] 还没有枪" % self.name)
return
# 喊一声口号
print("同志们,冲啊!!")
# 装填子弹
self.gun.add_bullet(40)
# 射击
self.gun.shoot()
ak47 = Gun("AK47")
xusanduo = Soldier("许三多")
xusanduo.gun = ak47
xusanduo.fire()
身份运算符用于比较两个对象的内存地址是否一致——是否是同一个对象的引用
Python
中针对None
比较时,建议使用is
判断运算符 | 描述 | 实例 |
---|---|---|
is | is是判断两个标识符是不是引用同一个对象 | x is y,类似id(x) == id(y) |
is not | is not是判断两个标识符是不是引用不同对象 | x is not y,类似id(a)!=id(b) |
is 与 == 区别
is
用于判断两个变量引用对象是否为同一个
==
用于判断引用变量的值是否相等
应用场景:
定义方式:
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
提示:在日常开发中,不要使用这种方式,访问对象的私有属性或私有方法
在Python中,并没有真正意义的私有
_类名
=>_类名_名称
面向对象三大特性:
继承的概念:子类拥有父类的所有方法和属性
2)专业术语
Dog
类是Animal
类的子类,Animal
类是Dog
类的父类,Dog
类从Animal
类继承Dog
类是Animal
类的派生类,Animal
类是Dog
类的基类,Dog
类从Animal
类派生3)继承的传递性
子类拥有父类以及父类的父类中封装的所有属性和方法
应用场景
当父类的方法实现不能满足子类需求时,可以对方法进行重写(overwrite)
重写父类方法有两种情况
1)覆盖父类的方法
具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
2)对父类方法进行扩展
super().父类方法
来调用父类方法的执行关于super
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 bark(self):
# 针对子类特有的需求,编写代码
print("哮天犬叫!")
# 使用super(). 调用原本在父类中封装的方法
super().bark()
# 增加其他子类的代码
print("吃月亮!")
xtq = XiaoTianQuan()
xtq.bark()
调用父类方法的另外一种方式:
在Python2.x中,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
Pyrhon3.x
还支持这种方式提示
父类名
和super()
两种方式不要混用B
的对象不能直接访问__num2
属性B
的对象不能再demo
方法内访问__num2
属性B
的对象不可以在demo
方法内,调用父类的test
方法test
方法内部,能够访问__num2
属性和__test
方法概念
问题提出:
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
提示:在开发时,应该尽量避免这种容易产生混淆的情况!如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承
Python中的MRO——方法搜索顺序
__mro__
可以查看方法的搜索顺序method resolution order
, 主要用于在多继承判断方法、属性的调用路径print(C.__mro__)
__mro__
的输出结果从左至右的顺序查找的示例:
class A:
def test(self):
print("A -- test")
def demo(self):
print("A -- demo")
class B:
def test(self):
print("B -- test")
def demo(self):
print("B -- demo")
class C(B, A):
pass
c = C()
c.test()
c.demo()
print(C.__mro__)
分析:
首先看print(C.__mro__)
的结果,其顺序是自己的类,继承的父类B,继承的父类A,object类(所有类的基类),所以在调用test()
和demo()
时,现在自己类里面找,没有,再去父类B找,找到了,执行
object是Python为所有对象提供的基类,提供有一些内置的属性和方法们可以使用
dir
函数查看
object
为基类的类,推荐使用object
为基类的类,不推荐使用新式类和经典类在多继承时 —— 会影响到方法的搜索顺序
为了保证编写的代码能够同时在Python 2.x 和Python 3.x 运行,今后在定义类时,如果没有父类,建议统一继承自object
面向对象三大特性
需求
Dog
类中封装方法game
XiaoTianDog
继承自Dog
,并且重写game
方法
Person
类,并且封装一个和狗玩的方法
game
方法示例:
from pyexpat.model import XML_CTYPE_SEQ
from unicodedata import name
class Dog:
def __init__(self, name) -> None:
self.name = name
def game(self):
print("%s 蹦蹦跳跳地玩耍.." % self.name)
class XiaoTianDog(Dog):
def game(self):
print("%s 飞到天上去玩耍.." % self.name)
class Person:
def __init__(self, name) -> None:
self.name = name
def game_with_dog(self, dog):
print("%s 和 %s 快乐地玩耍.." % (self.name, dog.name))
dog.game()
wangcai = Dog("旺财")
xtq = XiaoTianDog("哮天犬")
xiaoming = Person("小明")
xiaoming.game_with_dog(wangcai)
xiaoming.game_with_dog(xtq)
结果:
分析:
在Person
的方法里,写的其实是和父类的Dog
玩耍,而解释器会根据实际传入的对象来决定调用父类还是某一个子类,这就是多态