python基础07-面向对象1:类和对象、魔方方法

面向对象1:类和对象、魔方方法

一、面向对象编程

1. 面向过程

  • 把编程任务划分成一个一个的步骤,然后按照步骤分别去执行。
    • 小明起床上学 为例,面向过程则做了如下 4 件事:起床、穿衣、洗漱、上学,这 4 个过程的顺序很重要,须一个一个地实现。
  • 对于面向过程的思想: 需要实现一个功能的时候,看重的是开发的步骤和过程,每一个步骤都需要自己亲力亲为。
    • 以 吃饭 举例:买菜、洗菜、煮饭、切菜……
  • 面向过程编程适合开发中小型项目

2. 面向对象

  • 把构成问题事务分解成各个

    对象

    ,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题步骤中的行为。

    • 小明起床上学 为例,我们关心的是小明这个对象,而不是 起床、穿衣、洗漱、上学 这 4 个过程,这 4 个过程是小明这个对象的一部分,只是其中的一种行为,而且对于行为的顺序没有强制要求。
  • 对于面向对象的思想:当需要实现一个功能的时候,看重的并不是过程和步骤,而是关心的是对象,对象能做啥

    • 以 吃饭 举例:找 饭馆 对象,饭馆提供菜和饭,不关心做菜和煮饭的内部过程
  • 面向对象编程适合开发大型项目

二、类和对象

1. 类

  • 很多事物存在 相同的属性和行为(也叫方法),比如人有姓名年龄,能吃饭睡觉等等。
  • 描述 具有共同特征的事物的 抽象,称为 (class)。
  • 类包含两个组成部分:
    • 属性:比如姓名,年龄,身高,肤色等
    • 方法:比如吃饭,睡觉,飞行,歌唱等

2. 对象

  • 对象 的实例,是具体的实体

3. 类和对象的关系

  • 类是对象的模板(不占内存空间),对象是类的实例(占内存空间)。
    • 类相当于图纸,对象相当于根据图纸制造的实物。
  • 每个对象必须有一个对应的类。

三、定义类

1. 定义类

定义一个类,格式如下:

#class 类名:
#class 类名(): # 前2个旧式写法(经典类),不推荐
class 类名(object):
    方法列表(不是真的是列表,只是多个函数的定义)

说明:

  • 定义类时有 2 种形式:经典类和新式类,前两行注释部分则为经典类,推荐写新式类
  • object 是Python 里所有类的最顶级父类
  • 类名 的命名规则按照 驼峰命名法

2. 定义方法

定义方法(方法也叫实例方法)格式为:

class 类名(object):
    def 方法名(self): 
        ...
  • 方法的格式和函数类似,也可以设置参数和返回值,但是 需要设置第一个参数为 self
  • 编辑器一般会自动生成self ,后面会讲解 self 的作用

示例代码:

class Dog(object):
    # self自动添加的,后面有讲解
    # 定义在类里面的函数,叫方法
    def eat(self):
        print('吃骨头')

    def drink(self):
        print('喝水')

四、创建对象和调用方法

1. 创建对象

  • Python中,可以根据已经定义的类去创建示例对象。

  • 创建对象的格式为:

    对象变量名 = 类名()

示例代码:

# 定义类型
class Dog(object):
    # 定义在类里面的函数,叫方法
    def eat(self):
        print('吃骨头')

    def drink(self):
        print('喝水')

# 创建对象格式:实例对象变量名 = 类名()
dog1 = Dog()

2. 调用方法

调用方法的格式为:

对象变量名.方法名()
  • 注意:虽然定义方法时设置第一个参数 self,但是 调用方法时不要传递对应self的参数,解释器自动处理

示例代码:

class Dog(object):
    # 定义在类里面的函数,叫方法
    def eat(self):
        print('吃骨头')

    def drink(self):
        print('喝水')


# 创建对象格式:实例对象变量名 = 类名()
dog1 = Dog()

# 对象变量.方法名字(), self不用处理
dog1.eat()
dog1.drink()

3. 创建多个对象

  • 类作为对象的模具,根据类可以创建多个对象
class Dog(object):
    # 定义在类里面的函数,叫方法
    def eat(self):
        print('吃骨头')

    def drink(self):
        print('喝水')

# 对象1 = 类名()
# 对象2 = 类名()

# 对象1
dog1 = Dog()
dog1.eat()
dog1.drink()

# 对象2
dog2 = Dog()
dog2.eat()
dog2.drink()

五、添加和使用属性

1. 添加和使用属性

  • 对象既然有实例方法,也有自己的属性。
  • 定义/添加属性格式:
对象变量名.属性名 = 数据
  • 属性和变量类似,首次赋值时会定义属性,再次赋值改变属性

示例代码:

class Dog(object):
    # 定义在类里面的函数,叫方法
    def eat(self):
        print('吃骨头')

    def drink(self):
        print('喝水')


# 1. 创建对象变量
dog1 = Dog()

# 2. 对象变量.属性 = 数值
dog1.age = 3

# 打印属性
print(dog1.age)

# 修改属性
dog1.age = 2
print(dog1.age)

六、self的作用

1. self是什么

  • 在Python类中规定,实现方法的第一个参数是实例对象本身,并且约定俗成,把其名字写为self。

  • 某个对象调用其方法时,Python解释器会

    自动把这个对象

    作为第一个参数传递给方法

    • 通俗理解:哪个对象调用方法,方法中self就是这个对象

示例代码:

# 定义类
class Dog(object):
    def print_info(self):
        print('测试self', id(self))


# 创建对象1
dog1 = Dog()

# 打印dog1的id
print("调用方法前", id(dog1))

# dog1调用print_info, print_info的self就是dog1
# 底层调用:print_info(dog1), 解释器自动把dog1传给方法中的self
dog1.print_info()

print("调用方法后", id(dog1))

print('='*30)
# 创建对象2
dog2 = Dog()

print("调用方法前", id(dog2))
dog2.print_info()
print("调用方法后", id(dog2))

运行结果:

调用方法前 4400762352
测试self 4400762352
调用方法后 4400762352
==============================
调用方法前 4400762544
测试self 4400762544
调用方法后 4400762544

2. self的作用

  • 在方法中使用 self,可以获取到调用当前方法的对象,进而获取到该对象的属性和方法
  • self作用:为了区分不同对象的属性和方法
# 定义类
class Dog(object):
    def print_info(self):
        print(self.type)


# 创建对象,实例化对象
dog1 = Dog()
# 添加属性
dog1.type = '大黄狗'
# 直接调用方法
dog1.print_info()

print('='*30)
dog2 = Dog()
dog2.type = '旺财'
dog2.print_info()

七、魔法方法:init()

1. 魔法方法

  • 在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method ,中文称 魔法方法
  • 魔法方法是系统提供好的方法名字,用户需重新实现它
  • 魔法方法一般情况下无需手动调用,在合适时候自动会调用

2. __init__()基本使用

  • __init__()方法叫做 对象的初始化方法,在 创建一个对象后会被自动调用,不需要手动调用
  • __init__()方法的作用:添加属性

示例代码:

"""
__init__方法:
    1. 作用:添加属性
    2. 特点:创建对象的时候,实例化对象,自动调用__init__方法
"""


class Dog(object):
    def __init__(self):
        """初始化函数,添加属性"""
        self.type = '大黄狗'
        print('init方法调用了')

    def print_info(self):
        """打印实例属性"""
        print(self.type)


# 1. 创建对象,实例化对象,自动调用__init__方法
# 2. 尽管是自动调用方法,也是dog1自动调用方法,所以,self就是dog1
dog1 = Dog()

# 手动调用实例方法
dog1.print_info()

运行结果:

init方法调用了
大黄狗

3. __init__()自定义参数

  • __init__(self)除了默认参数self,还可以设置任意个数的自定义参数,例如:__init__(self,x,y,z)
  • init方法 设置的自定义参数必须和创建对象时传递的参数保持一致,例如:对象变量名 = 类名(x,y,z)
  • 开发者可以 设置自定义参数,为对象的默认属性提供 不同的初始值

示例代码:

class Dog(object):
    def __init__(self, _type):
        """初始化函数,添加属性"""
        self.type = _type
        print('init方法调用了')

    def print_info(self):
        """打印实例属性"""
        print(self.type)


# 创建对象,实例化对象,自动调用__init__方法
# '旺财'传递给 _type 形参
dog1 = Dog('旺财')
# 直接调用方法
dog1.print_info()

print('='*30)
dog2 = Dog('来福')
dog2.print_info()

运行结果:

init方法调用了
旺财
==============================
init方法调用了
来福

八、魔法方法:str()

1. str()方法的使用

  • 如果直接 print 打印对象,会看到创建出来的对象在内存中的地址
  • 当使用print(对象变量名)输出对象的时候,只要类中定义了__str__()方法,就会打印__str__()方法返回值
  • __str__()方法作用主要返回对象属性信息,print(对象变量名)输出对象时直接输出__str__()方法返回的描述信息
  • __str__()方法的返回值必须是 字符串类型

示例代码:

"""
__str__方法:
    1. 返回值必须是字符串类型
    2. print(对象变量名)  对象变量名的位置替换为__str__()方法返回值的内容
"""


class Dog(object):
    # 添加属性,type, age
    def __init__(self, _type, _age):
        self.type = _type
        self.age = _age

    # 测试有定义 __str__ 方法,和没有定义,print(对象)的区别
    def __str__(self):
        return f'类型{self.type},年龄{self.age}'
        # return "类型:%s, 年龄:%d" % (self.type, self.age)


# 创建对象
dog1 = Dog('大白狗', 3)

# 如果有__str__ 方法,dog1的位置替换为__str__()方法返回值的内容
print(dog1)
print('对象描述信息为:', dog1)

运行结果:

类型:大白狗, 年龄:3
对象描述信息为: 类型:大白狗, 年龄:3

九、魔法方法:__del__()

1. del()方法的使用

  • 在对象的生命周期结束(对象销毁)时, __del__()方法会自动被调用,做一些清理工作
class Dog(object):
    def __del__(self):
        print('我悄悄地离开了')


# 设计一个函数,在函数内容创建对象
# 函数调用完毕,里面创建的对象,生命周期结束,自动调用__del__方法
def func():
    dog1 = Dog()


print('函数调用前')
# 调用函数
func()
print('函数调用后')

运行结果:

函数调用前
我悄悄地离开了
函数调用后

应用:烤地瓜

1. 需求说明

烤地瓜规则:

  1. 地瓜有自己的状态,默认是生的,地瓜可以进行烧烤
  2. 地瓜有自己烧烤的总时间,由每次烧烤的时间累加得出
  3. 地瓜烧烤时,需要提供本次烧烤的时间
  4. 地瓜烧烤时,地瓜状态随着烧烤总时间的变化而改变:[0, 3) 生的、[3, 6) 半生不熟、[6, 8) 熟了、>=8 烤糊了
  5. 输出地瓜信息时,可以显示地瓜的状态和烧烤的总时间

2. 面向对象设计

2.1 地瓜类

  1. 使用 SweetPotato 类可以创建 地瓜对象
  2. 地瓜有两个属性:
    • 状态 state:字符串
    • 烧烤总时间 cooked_time:整数
  3. 定义cook方法, 提供参数time设置本次烧烤的时间
    • 使用 本次烧烤时间烧烤总时间 进行 累加
    • 根据烧烤总时间, 设置地瓜的状态:
      • [0, 3) -> 生的
      • [3, 6) -> 半生不熟
      • [6, 8) -> 熟了
      • 大于等于8 -> 烤糊了

2.2 主程序逻辑

  1. 创建 地瓜对象
  2. 分多次 烧烤地瓜
  3. 每烧烤一次,输出地瓜信息

2.3 示例代码

2.3.1 步骤流程
"""
# SweetPotato 类的设计
    地瓜有两个属性:
        状态 state:字符串
        烧烤总时间 cooked_time:整数

# 1. 定义__init__方法,添加2个属性
    # 1.1 默认状态state是生的
    # 1.2 默认时间cooked_time是0

# 2. 定义__str__方法
    # 2.1 返回地瓜状态,烧烤总时间

# 3. 定义 cook 方法, 提供参数 time 设置 本次烧烤的时间
    # 3.1 使用 本次烧烤时间 对 烧烤总时间 进行 累加
    # 3.2 根据 烧烤总时间, 设置地瓜的状态:
        [0, 3) -> 生的
        [3, 6) -> 半生不熟
        [6, 8) -> 熟了
        大于等于8 -> 烤糊了

# 4. 主逻辑程序
# 4.1 创建 地瓜对象
# 4.2 分多次烧烤地瓜
# 4.3 每烧烤一次,输出地瓜信息
"""
2.3.2 定义地瓜类、通过__init__()方法添加属性
class SweetPotato(object):
    """地瓜类"""
    # 1. 定义__init__方法,添加2个属性
    def __init__(self):
        # 1.1 默认状态state是生的
        self.state = '生的'
        # 1.2 默认时间cooked_time是0
        self.cooked_time = 0
2.3.3 定义__str__()方法
    # 2. 定义__str__方法
    def __str__(self):
        # 2.1 返回地瓜状态,烧烤总时间
        return f'地瓜状态为:{self.state}, 烧烤总时间为:{self.cooked_time} 分钟'
2.3.4 定义”烤地瓜”方法
    # 3. 定义 cook 方法, 提供参数 time 设置 本次烧烤的时间
    def cook(self, time):
        # 3.1 使用 本次烧烤时间 对 烧烤总时间 进行 累加
        self.cooked_time += time

        # 3.2 根据 烧烤总时间, 设置地瓜的状态:
        if 0 <= self.cooked_time < 3:
            self.state = '生的'
        elif 3 <= self.cooked_time < 6:
            self.state = '半生不熟'
        elif 6 <= self.cooked_time < 8:
            self.state = '熟了'
        else:
            self.state = '烤糊了'
2.3.5 测试代码

把上面 3 块代码合并为一个程序后,在代码的下面添加以下代码进行测试:

# 4.1 创建 地瓜对象
sp = SweetPotato()
print(sp)  # 输出地瓜信息

# 4.2 分多次烧烤地瓜
sp.cook(2)
print(sp)  # 4.3 每烧烤一次,输出地瓜信息

sp.cook(5)
print(sp)

sp.cook(2)
print(sp)

运行结果:

地瓜状态为:生的, 烧烤总时间为:0 分钟
地瓜状态为:生的, 烧烤总时间为:2 分钟
地瓜状态为:熟了, 烧烤总时间为:7 分钟
地瓜状态为:烤糊了, 烧烤总时间为:9 分钟

3. 拓展功能

3.1 烤地瓜需求拓展

  1. 地瓜可以添加佐料,如 盐、孜然、辣酱等
  2. 输出地瓜信息时,可以显示地瓜的状态、烧烤总时间、以及添加过的所有佐料

3.2 需求分析

  • 每个地瓜记录自己的佐料,定义属性来记录
  • 地瓜可以添加多个佐料,应该定义 容器类型属性 来记录添加的佐料
  • 地瓜类 应该 定义方法来实现添加佐料的功能

3.3 示例代码

3.3.1 步骤流程
# 5. 拓展功能
# 5.1 添加属性 condiments, 列表类型,默认为空列表

# 5.2 修改 __str__ 返回信息,返回增加已添加的佐料信息

# 5.3 定义 add_condiments(self, temp), temp为添加什么佐料的参数
    # 5.3.1 佐料列表追加元素

# 5.4 再次测试代码,添加佐料,重新打印信息
3.3.2 添加 condiments 属性
    # 1. 定义__init__方法,添加3个属性
    def __init__(self):
        # 1.1 默认状态state是生的
        self.state = '生的'
        # 1.2 默认时间cooked_time是0
        self.cooked_time = 0
        # 5.1 添加属性 condiments, 列表类型,默认为空列表
        self.condiments = []
3.3.3 修改__str__()方法,在方法中使用condiments属性显示已添加的佐料
    # 2. 定义__str__方法
    def __str__(self):
        # 2.1 返回地瓜状态,烧烤总时间
        # 5.2 修改 __str__ 返回信息,返回增加已添加的佐料信息
        return f'地瓜状态为:{self.state}, 烧烤总时间为:{self.cooked_time} 分钟,包括的佐料为:{self.condiments}'
3.3.4 定义add_condiments()方法
    # 5.3 定义 add_condiments(self, temp), temp为添加什么佐料的参数
    def add_condiment(self, temp):
        # 5.3.1 佐料列表追加元素
        self.condiments.append(temp)
3.3.5 再次测试
# 4.1 创建 地瓜对象
sp = SweetPotato()
print(sp)  # 输出地瓜信息

# 4.2 分多次烧烤地瓜
sp.cook(2)
# 添加佐料
sp.add_condiment('番茄酱')
print(sp)  # 4.3 每烧烤一次,输出地瓜信息

sp.cook(5)
sp.add_condiment('孜然')
print(sp)

sp.cook(2)
sp.add_condiment('烤肉')
print(sp)

运行结果:

地瓜状态为:生的, 烧烤总时间为:0 分钟,包括的佐料为:[]
地瓜状态为:生的, 烧烤总时间为:2 分钟,包括的佐料为:['番茄酱']
地瓜状态为:熟了, 烧烤总时间为:7 分钟,包括的佐料为:['番茄酱', '孜然']
地瓜状态为:烤糊了, 烧烤总时间为:9 分钟,包括的佐料为:['番茄酱', '孜然', '烤肉']

应用: 搬家具

1. 需求说明

搬家具规则:

  1. 家具分不同的类型,并占用不同的面积
  2. 输出家具信息时,显示家具的类型和家具占用的面积
  3. 房子有自己的地址和占用的面积
  4. 房子可以添加家具,如果房子的剩余面积可以容纳家具,则提示家具添加成功;否则提示添加失败
  5. 输出房子信息时,可以显示房子的地址、占地面积、剩余面积

2. 面向对象设计

2.1 家具类

  1. 使用 Item 类可以创建 家具对象

  2. 家具有两个属性:

    • 家具类型 type:字符串
    • 家具面积 area:整数
  3. 实现

    __str__

    方法

    • 显示家具的 type 和 area 属性

2.2 房子类

  1. 使用 Home 类可以创建 房子对象

  2. 房子有三个属性:

    • 地址 address:字符串
    • 房子面积 area:整数
    • 房子剩余面积 free_area:整数,默认为房子的面积
  3. 实现

    __str__

    方法

    • 显示房子的 address 、area、free_area 属性
  4. 实现

    add_item

    方法,提供

    item

    参数来添加家具

    • 如果
 可以容纳家具:

 - 打印添加家具的类型和面积
 - **剩余面积** 减少
  • 如果 不能容纳家具: 提示家具添加失败

2.3 主程序逻辑

  1. 创建 家具对象, 输出 家具信息
  2. 创建 房子对象, 输出 房子信息
  3. 房子添加家具, 输出 房子信息

2.4. 示例代码

"""
家具类 Item
# 1. 定义__init__方法,添加2个属性,需要2个形参 _type, _area
    # 1.1 家具类型 type
    # 1.2 家具面积 area

# 2. 实现__str__方法
    # 2.1 返回家具类型和家具面积

房子类 Home
# 1. 定义__init__方法,添加3个属性,需要3个形参
    # 1.1 地址 address
    # 1.2 房子面积 area
    # 1.3 房子剩余面积 free_area,默认为房子的面积

# 2. 实现__str__方法
    # 2.1 返回房子地址、面积、剩余面积信息

# 3. 实现add_item方法,提供item参数来添加家具,item是对象
    # 3.1 如果 房间的剩余面积 >= 家具的面积,可以容纳家具:
        # 3.1.1 打印添加家具的类型和面积
        # 3.1.2 剩余面积 减少
    # 3.2 否则 不能容纳家具:提示家具添加失败

主程序逻辑:
# 1. 创建 家具对象, 输出 家具信息
# 2. 创建 房子对象, 输出 房子信息
# 3. 房子添加家具, 输出 房子信息
"""


class Item(object):
    """家具类"""

    # 1. 定义__init__方法,添加2个属性,需要2个形参 _type, _area
    def __init__(self, _type, _area):
        # 1.1 地址 address
        self.type = _type
        # 1.2 家具面积 area
        self.area = _area

    # 2. 实现__str__方法
    def __str__(self):
        # 2.1 返回房子地址、面积、剩余面积信息
        return f'家具类型为:{self.type}, 家具面积为:{self.area}'


class House(object):
    """房子类"""

    # 1. 定义__init__方法,添加3个属性,需要3个形参
    def __init__(self, _addr, _area, _free_area):
        # 1.1 地址 address
        self.address = _addr
        # 1.2 房子面积 area
        self.area = _area
        # 1.3 房子剩余面积 free_area,默认为房子的面积
        self.free_area = _free_area

    # 2. 实现__str__方法
    def __str__(self):
        # 2.1 返回房子地址、面积、剩余面积信息
        return f'房子地址:{self.address}, 房子面积:{self.area}, 房子剩余面积:{self.free_area}'

    # 3. 实现add_item方法,提供item参数来添加家具,item是家具对象
    def add_item(self, item):
        # 3.1 如果 房间的剩余面积 >= 家具的面积,可以容纳家具:
        if self.free_area > item.area:
            # 3.1.1 打印添加家具的类型和面积
            print(f'添加{item.type}成功了,家具占用面积为:{item.area}')
            # 3.1.2 剩余面积 减少
            self.free_area -= item.area

        # 3.2 否则 不能容纳家具: 提示家具添加失败
        else:
            print('家具添加失败')


# 1. 创建 家具对象, 输出 家具信息
# 2. 创建 房子对象, 输出 房子信息
# 3. 房子添加家具, 输出 房子信息
h = House('北京', 70, 70)  # 创建房间对象h
print(h)  # 打印房子对象,获取House中__str__返回的信息

tv = Item('超大电视', 10)  # 创建家具1
print(tv)  # 打印家具对象,输出Item中__str__返回的信息
h.add_item(tv)  # 房子添加家具
print(h)  # 打印房子对象,获取House中__str__返回的信息

bed = Item('超大床', 20)  # 创建家具2
print(bed)  # 打印家具对象,输出Item中__str__返回的信息
h.add_item(bed)  # 房子添加家具
print(h)

运行结果:

房子地址:北京, 房子面积:70, 房子剩余面积:70
家具类型为:超大电视, 家具面积为:10
添加超大电视成功了,家具占用面积为:10
房子地址:北京, 房子面积:70, 房子剩余面积:60
家具类型为:超大床, 家具面积为:20
添加超大床成功了,家具占用面积为:20
房子地址:北京, 房子面积:70, 房子剩余面积:40