面向对象 OOP
Object Oriented Programming 面向对象编程
面向过程
- 把一个完整的需求所有步骤,从头到尾逐步实现
- 将某些独立功能,封装成函数
- 完成整个需求就是顺序调用已封装完成的各个函数
特点
- 注重步骤与过程,不注重职责分工
- 如果需求复杂,代码会变得很复杂
- 开发复杂项目的时候,没有固定的套路,导致开发难度变得很大
面向对象
- 在完成一个需求的时候,首先明确职责,即要做的事情 (方法)
- 根据职责确定不同的对象,并在对象内封装不同的方法
- 完成整个需求就是顺序的创建不同的对象并调用不同的方法
特点
- 注重对象的创建和方法的实现
- 针对复杂需求,提供固定的套路
- 需要在面向过程的基础上,学习面向对象的语法
面向对象编程
类
类是对一群具有相同特征和行为的事物的统称,是抽象的,不能直接使用
- 特征,即属性
- 行为,即方法
对象
对象是由类创建的具体存在,可以直接使用
- 由哪个类创建的对象,就拥有这个类中定义的属性
- 由哪个类创建的对象,就可以调用这个类的方法
类和对象的关系
- 类是模板,对象是根据这个类创建出来的,先有类再有对象
- 类只有一个,但是可以产生多个对象,这些对象的属性可能各不相同
- 类中定义了几个方法,对象就可以调用几个方法,不可能多,也不可能少
类的设计
在程序设计中,要设计一个类,需要满足以下条件
- 类名,事物的名称,一般为名词并满足大驼峰命名法
- 属性,这类事物具有什么特征
- 方法,这类事物具备哪些功能,一般为动词
需求中没有提及的属性和方法,不需要定义
使用Python实现类的定义和对象的创建
dir内置函数
在Python中,函数、变量、数据都是对象。
使用dir内置函数,传入 标识符/数据 ,可以查看对象内的所有属性和方法
def demo():
"""
这是一个简单的测试函数
:return:
"""
print('hello world')
print('使用dir()函数查看demo的内置方法')
init_function = dir(demo)
for i in init_function:
print(i)
使用dir()函数查看demo的内置方法
__annotations__
__builtins__
__call__
__class__
__closure__
__code__
__defaults__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__get__
__getattribute__
__getstate__
__globals__
__gt__
__hash__
__init__
__init_subclass__
__kwdefaults__
__le__
__lt__
__module__
__name__
__ne__
__new__
__qualname__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__type_params__
__xxx__是Python 提供的内置方法/属性
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
1 | __new__ | 方法 | 创建对象时,会被自动调用 |
2 | __init__ | 方法 | 对象被初始化时,会被自动调用 |
3 | __del__ | 方法 | 对象被从内存销毁时,会被自动调用 |
4 | __str__ | 方法 | 返回对象的描述信息,print函数输出时使用 |
定义简单的类和方法
类的定义
- 使用 class 关键字定义类,类名符合驼峰命名
- 在类中定义的方法,与普通函数一致,但是方法的第一个参数必须为self
# 定义一个简单的类
class 类名:
"""
类中定义方法的时候,第一个参数必为self
"""
def 方法名1(self):
pass
def 方法名2(self):
pass
举例:定义一个赛亚人类
class SaiYan:
def fight(self):
print("战斗是赛亚人的天性")
def eat(self):
print("赛亚人吃的特别多")
创建对象并调用类的方法
创建对象
对象名 = 类()
举例:
kakarotto = SaiYan()
vegeta = SaiYan()
调用方法
kakarotto.eat()
kakarotto.fight()
vegeta.eat()
vegeta.fight()
对象的引用
- 创建对象的时候,对象名存储的仍然是对象在内存中的地址
- 在上述例子中,kakarotto变量引用了新建的SaiYan对象
- 使用print(对象名)的方法可以查看对象的引用
print(kakarotto)
# <__main__.SaiYan object at 0x00000274A0A0D640>
上述代码展示了, kakarotto 是由 SaiYan 类创建的,并且在内存的十六位地址为0x00000274A0A0D640
使用 id(变量名) 方法也可以查看变量名的引用地址,也可以用"%d"十进制输出地址或"%x"十六进制输出地址
addr = id(kakarotto)
print("%d" % addr)
print("%x" % addr)
2167256373328
1f89a8bd850
创建多个对象
- 可以使用 不同的变量名 = 类() 语句创建多个对象
- 不同对象的引用地址不同
- 如果使用 对象A = 对象B 这样的赋值语句,则 对象A的引用 = 对象B的引用
kakarotto = SaiYan()
kakarotto2 = SaiYan()
print("赋值之前两个变量的引用地址")
print(kakarotto)
print(kakarotto2)
kakarotto2 = kakarotto
print("赋值之后两个变量的引用地址")
print(kakarotto2)
print(kakarotto)
赋值之前两个变量的引用地址
<__main__.SaiYan object at 0x0000021EF87ED6A0>
<__main__.SaiYan object at 0x0000021EF87ED670>
赋值之后变量的引用地址
<__main__.SaiYan object at 0x0000021EF87ED6A0>
<__main__.SaiYan object at 0x0000021EF87ED6A0>
方法中的self参数是什么?
要理解self参数,首先要知道属性是什么
给对象增加属性[在类的外部]
- 通过 对象.属性名称 的方式,可以为对象增加属性
kakarotto.hair_style = 'short'
kakarotto.hair_color = 'black'
但是这种方法在python中,不推荐使用
利用self在类的封装方法中输出属性
self参数保存的是对象的引用地址,可以理解为:哪个对象调用了方法,方法的第一个参数self保存这个对象的引用。
class SaiYan:
def fight(self):
print("战斗是 %s 的天性" % self.name)
def eat(self):
print("赛亚人吃的特别多")
kakarotto = SaiYan()
vegeta =SaiYan()
kakarotto.name = 'kakarotto'
vegeta.name = 'vegeta'
kakarotto.fight()
vegeta.fight()
战斗是 kakarotto 的天性
战斗是 vegeta 的天性
- 在调用方法时,不需要传递self参数
- 在方法内部,可以通过self方法获取属性,也可以通过self方法调用其他的对象方法
class SaiYan:
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
kakarotto = SaiYan()
vegeta = SaiYan()
kakarotto.name = 'kakarotto'
vegeta.name = 'vegeta'
kakarotto.fight()
vegeta.fight()
战斗是 kakarotto 的天性
kakarotto 吃的特别多
战斗是 vegeta 的天性
vegeta 吃的特别多
使用对象定义属性的局限性
- 使用对象定义属性,并不能在类中生成新的属性,也不能使其他对象具有这个属性
- 在代码执行过程中,如果没有找到对象的属性,会导致代码报错
class SaiYan:
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
kakarotto = SaiYan()
vegeta = SaiYan()
kakarotto.name = 'kakarotto'
kakarotto.fight()
vegeta.fight()
AttributeError: 'SaiYan' object has no attribute 'name'
战斗是 kakarotto 的天性
kakarotto 吃的特别多
- 对象的属性应该封装在对象的内部
初始化方法
- 在使用
类名()
创建对象的时候,会执行以下操作- 为对象分配内存空间 – 创建对象
- 为对象的属性设置初始值 --调用
__init__
对象的内置方法
3.__init__
初始化方法用来定义一个类 具有哪些属性的方法
在 SanYan 类中重写__init__
方法
class SaiYan:
def __init__(self):
print("这个是赛亚人的初始化方法")
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
kakarotto = SaiYan()
这个是赛亚人的初始化方法
在初始化方法中定义属性
class SaiYan:
def __init__(self):
print("这个是赛亚人的初始化方法")
self.name = "kakarotto"
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
kakarotto = SaiYan()
kakarotto.fight()
kakarotto.eat()
这个是赛亚人的初始化方法
战斗是 kakarotto 的天性
kakarotto 吃的特别多
kakarotto 吃的特别多
初始化方法定义属性的改造
- 在上述代码中,
__init__
方法中定义的 name属性默认值均为 kakartto,不符合实际要求 - 通过新增参数的方式,可以完成对
__init__
方法的改造
class SaiYan:
def __init__(self,init_name):
print("这个是赛亚人 %s 的初始化方法" % init_name)
self.name = init_name
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
kakarotto = SaiYan("kakarotto")
vegeta = SaiYan("vegeta")
kakarotto.fight()
vegeta.eat()
这个是赛亚人 kakarotto 的初始化方法
这个是赛亚人 vegeta 的初始化方法
战斗是 kakarotto 的天性
kakarotto 吃的特别多
vegeta 吃的特别多
- 在
__init__
方法内部,使用 self.属性 = 形参 ,来接收传递的参数 - 在创建对象时,采用 类名(属性1,属性2,属性3 ···)的方式来初始化对象
- 这个方法类似于Java中,类的构造方法
对象的其他内置方法及生命周期
__del__
内置方法
- 对于一个对象
- 当使用 类名 () 创建对象时,调用
__init__
方法,完成对象的初始化创建 - 使用
__del__
方法,在对象从内存销毁之前,可以完成一些事情
- 当使用 类名 () 创建对象时,调用
一个对象的生命周期
- 开始:使用 类名 () 创建对象
- 结束:
__del__
方法调用完成 - 在对象的生命周期内,可以访问其属性,调用其方法
class SaiYan:
def __init__(self, init_name):
print("这个是赛亚人 %s 的初始化方法" % init_name)
self.name = init_name
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
def __del__(self):
print("%s 对象被销毁" % self.name)
kakarotto = SaiYan("kakarotto")
vegeta = SaiYan("vegeta")
kakarotto.fight()
vegeta.eat()
del vegeta # 主动删除这个对象
print("-" * 50)
这个是赛亚人 kakarotto 的初始化方法
这个是赛亚人 vegeta 的初始化方法
战斗是 kakarotto 的天性
kakarotto 吃的特别多
vegeta 吃的特别多
vegeta 对象被销毁
--------------------------------------------------
kakarotto 对象被销毁
__str__
内置方法
- 默认使用print函数,打印一个对象时,会返回这个对象的类及对象十六进制的引用地址
- 使用
__str__
这个内置方法,返回一个字符串,可以实现自定义打印对象的内容 __str__
方法必须返回一个字符串- 类似Java重写
toString()
方法
class SaiYan:
def __init__(self, init_name):
print("这个是赛亚人 %s 的初始化方法" % init_name)
self.name = init_name
def fight(self):
print("战斗是 %s 的天性" % self.name)
self.eat()
def eat(self):
print("%s 吃的特别多" % self.name)
def __del__(self):
print("%s 对象被销毁" % self.name)
def __str__(self):
return "i am %s" % self.name
kakarotto = SaiYan("kakarotto")
vegeta = SaiYan("vegeta")
print(kakarotto)
print(vegeta)
这个是赛亚人 kakarotto 的初始化方法
这个是赛亚人 vegeta 的初始化方法
i am kakarotto
i am vegeta
kakarotto 对象被销毁
vegeta 对象被销毁
封装
- 封装是面向对象编程的最大特点
- 将方法和属性封装到一个抽象的类中
- 外界使用类创建对象,让对象去调用方法
- 对象中,方法的实现细节都在类内部
# 定义一个赛亚人类,这个类有两个属性:姓名name ,体重weight
# 赛亚人有两个方法:打架 fight ,吃饭 eat
# 每次打架 weight -1 ,每次吃饭 weight +0.5
class SanYan:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def fight(self):
self.weight -= 1
print("%s 的体重减少了1" % self.name)
def eat(self):
self.weight += 0.5
print("%s 的体重增加了0.5" % self.name)
def __str__(self):
return "我是 %s, 体重是 %s" % (self.name, self.weight)
def __del__(self):
print("%s 被销毁" % self.name)
kakarotto = SanYan("kaka", 100)
print(kakarotto)
kakarotto.fight()
print(kakarotto)
kakarotto.eat()
print(kakarotto)
print("-" * 20)
我是 kaka, 体重是 100
kaka 的体重减少了1
我是 kaka, 体重是 99
kaka 的体重增加了0.5
我是 kaka, 体重是 99.5
--------------------
kaka 被销毁
在对象的方法内部,可以直接访问方法的属性
给悟空造一个房子
# 房子有户型,总面积和家具名列表
# 家具有名字和占地面积,其中
# 床 占地 4 平方米
# 衣柜 占地 2 平方米
# 桌子 占地 1 平方米
# 将以上三件家具添加到房子中
# 打印房子时,要求输出 户型,总面积,剩余面积,家具名称列表
class House:
def __init__(self, house_type, full_area, furniture):
self.house_type = house_type
self.full_area = full_area
self.furniture_list = furniture
self.occupied_area = 0
def add_furniture(self, furniture):
rest_area = self.full_area - self.occupied_area
if rest_area > furniture.occupied_area:
self.furniture_list.append(furniture)
self.occupied_area += furniture.occupied_area
else:
print("添加家具失败,房间剩余面积 %f ,家具面积 %f")
def __str__(self):
furniture = ""
for i in self.furniture_list:
furniture += "\t"+i.name
return "房子的户型为 %s ,总面积 %0.2f ,剩余面积 %0.2f ,含有家具如下 %s" % (
self.house_type, self.full_area, self.full_area - self.occupied_area,furniture)
# 家具有名字和占地面积
class Furniture:
def __init__(self, name, occupied_area):
self.name = name
self.occupied_area = occupied_area
def __str__(self):
return self.name
# 床 占地 4 平方米
bed = Furniture("床", 4.0)
# 衣柜 占地 2 平方米
wardrobe = Furniture("衣柜", 2.0)
# 桌子 占地 1 平方米
table = Furniture("桌子", 2.0)
fu_house = House("福", 20.0, [])
fu_house.add_furniture(bed)
fu_house.add_furniture(wardrobe)
fu_house.add_furniture(table)
print(fu_house)
房子的户型为 福 ,总面积 20.00 ,剩余面积 12.00 ,含有家具如下 床 衣柜 桌子
士兵突击
# 1, 士兵许三多有一把AK47
# 2. 士兵可以开火
# 3. 枪能够发射子弹
# 4. 枪能够装填子弹
class Soldier:
def __init__(self, name, weapon=None):
self.name = name
self.weapon = weapon
def fire(self):
if self.weapon.bullet > 0:
self.weapon.fire()
else:
self.reload()
def reload(self):
self.weapon.reload()
class Gun:
def __init__(self, bullet):
self.bullet = bullet
def fire(self):
self.bullet -= 1
print("射出一发子弹")
def reload(self):
self.bullet += 1
print("装填一发子弹")
ak_47 = Gun(1)
xusanduo = Soldier("XU_SAN_DUO", ak_47)
xusanduo.fire()
xusanduo.fire()
射出一发子弹
装填一发子弹
一个对象的属性,可以是另一个类创建的对象
None关键字
- 上述例子中,使用了None关键字
- None 表示什么也没有
- 表示一个空对象,没有方法和属性,是一个特殊的常量
- 可以将None赋值给任意变量
- 判断用身份运算符 is / is not
- 含义与Java中的null相似
身份运算符
身份运算符用于比较两个对象的地址是否一致
运算符 | 描述 | 实例 |
---|---|---|
is | is是判断两个标识符是不是引用同一个对象 | x is y ,类似于 id(x) == id(y) |
is not | is not 是判断两个标识符是不是引用不同个对象 | x is not y ,类似于 id(x) != id(y) |
is 于 == 的区别
- is 用于判断引用对象是否为同一个
- == 用于判断引用变量的值是否相等
私有属性和私有方法
- 在定义类并创建对象的时候,有的属性或者方法只希望被对象内部使用,而不希望在外部被访问
- 使用
__xxx
定义私有属性 - 使用
__xxx()
定义私有方法
# 定义一个赛亚人类,这个类有两个属性:姓名name ,体重weight
# 赛亚人有两个方法:打架 fight ,吃饭 eat
# 每次打架 weight -1 ,每次吃饭 weight +0.5
class SanYan:
def __init__(self, name, weight):
self.name = name
self.weight = weight
self.__age = 18
def fight(self):
self.weight -= 1
print("%s 的体重减少了1" % self.name)
def eat(self):
self.weight += 0.5
print("%s 的体重增加了0.5" % self.name)
def __str__(self):
return "我是 %s, 体重是 %s" % (self.name, self.weight)
def __del__(self):
print("%s 被销毁" % self.name)
def __secret(self):
print("赛亚人的秘密是年龄为 %d" % self.__age)
kakarotto = SanYan("kaka", 100)
print(kakarotto)
kakarotto.fight()
print(kakarotto)
kakarotto.eat()
print(kakarotto)
print("-" * 20)
print(kakarotto.__age)
AttributeError: 'SanYan' object has no attribute '__age'
- 私有属性和私有方法只能在对象内部使用
- Python并没有真正意义的私有
- 给属性或者方法命名时,实际上是对名称做了特殊处理,使得外界无法访问
- 在名称前面加上
_类名
,如上述代码中_SanYan__secret()
,就可以访问私有方法了