【Python基础】11.面向对象-封装

面向对象 OOP

Object Oriented Programming 面向对象编程

面向过程

  • 把一个完整的需求所有步骤,从头到尾逐步实现
  • 将某些独立功能,封装成函数
  • 完成整个需求就是顺序调用已封装完成的各个函数

特点

  • 注重步骤与过程,不注重职责分工
  • 如果需求复杂,代码会变得很复杂
  • 开发复杂项目的时候,没有固定的套路,导致开发难度变得很大

面向对象

  • 在完成一个需求的时候,首先明确职责,即要做的事情 (方法)
  • 根据职责确定不同的对象,并在对象内封装不同的方法
  • 完成整个需求就是顺序的创建不同的对象调用不同的方法

特点

  • 注重对象的创建和方法的实现
  • 针对复杂需求,提供固定的套路
  • 需要在面向过程的基础上,学习面向对象的语法

面向对象编程

类是对一群具有相同特征和行为的事物的统称,是抽象的,不能直接使用

  • 特征,即属性
  • 行为,即方法

对象

对象是由类创建的具体存在,可以直接使用

  • 由哪个类创建的对象,就拥有这个类中定义的属性
  • 由哪个类创建的对象,就可以调用这个类的方法

类和对象的关系

  • 类是模板,对象是根据这个类创建出来的,先有类再有对象
  • 类只有一个,但是可以产生多个对象,这些对象的属性可能各不相同
  • 类中定义了几个方法,对象就可以调用几个方法,不可能多,也不可能少

类的设计

在程序设计中,要设计一个类,需要满足以下条件

  1. 类名,事物的名称,一般为名词并满足大驼峰命名法
  2. 属性,这类事物具有什么特征
  3. 方法,这类事物具备哪些功能,一般为动词

需求中没有提及的属性和方法,不需要定义

使用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 吃的特别多
  • 对象的属性应该封装在对象的内部
初始化方法
  • 在使用 类名()创建对象的时候,会执行以下操作
    1. 为对象分配内存空间 – 创建对象
    2. 为对象的属性设置初始值 --调用 __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__内置方法
  • 对于一个对象
    1. 当使用 类名 () 创建对象时,调用 __init__ 方法,完成对象的初始化创建
    2. 使用__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关键字
    1. None 表示什么也没有
    2. 表示一个空对象,没有方法和属性,是一个特殊的常量
    3. 可以将None赋值给任意变量
    4. 判断用身份运算符 is / is not
  • 含义与Java中的null相似

身份运算符

身份运算符用于比较两个对象的地址是否一致

运算符描述实例
isis是判断两个标识符是不是引用同一个对象x is y ,类似于 id(x) == id(y)
is notis 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并没有真正意义的私有
    1. 给属性或者方法命名时,实际上是对名称做了特殊处理,使得外界无法访问
    2. 在名称前面加上_类名,如上述代码中_SanYan__secret(),就可以访问私有方法了
  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值