1. 面向对象

面向对象编程–Object Oriented Programming,简写 OOP

面向对象三大特性:

  • 封装
  • 继承
  • 多态

1.1 基本概念

过程和对象:

  • 面向过程面向对象是两种不同编程方式
  • 过程是早期的一个编程概念
  • 过程类似于函数,只能执行,但没有返回值
  • 函数不仅能执行,还可以返回结果

面向过程

  • 把完成某一需求的所有步骤从头到尾逐步实现
  • 根据开发需求,将某些功能独立的代码封装成一个又一个函数
  • 最后完成的代码就是顺序的调用不同的函数

特点:

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

面向对象

相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法

  • 在完成一个需求前,首先确定职责–要做的事情(方法)
  • 根据职责确定不同的对象,在对象内部封装不同的方法(多个)
  • 最后完成的代码,就是顺序地让不同的对象调用不同的方法

特点:

  • 注重对象和职责,不同的对象承担不同的职责
  • 更加适合应对复杂的需求变化,是专门应对复杂项目的开发,有固定的套路
  • 需要在面向过程的基础上,再学习一些面向对象的语法

1.2 类和对象

概念

类(Class)

  • 是一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
    • 特征被称为属性
    • 行为被称为方法
  • 类就相当于制造飞机时的图纸,是一个模板,是负责创建对象

对象(object)

  • 对象是由类创建出来的一个具体存在,可以直接使用,又叫实例, 使用类创建对象的过程叫实例化
  • 由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
    • 属性
    • 方法
  • 对象就相当于用图纸制造飞机

类和对象的关系

  • 类是模板,对象是根据类这个模板创建出来的,应该先有类再有对象
  • 类只有一个,而对象可以有很多个
    • 不同的对象之间属性可能会各不相同
  • 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多也不可能少
    • 概念上下级不同

类是一个特殊的对象–类对象

Python 中一切皆对象

类对象可以拥有自己的:

  • 类属性
  • 类方法

设计

在使用面向对象开发前,首先应分析需求,确定程序中需要包含哪些类

设计类的三要素:

  • 类名:这类事物的名字,满足大驼峰命名法
  • 属性:这类事物具有什么样的特征
  • 方法:这类事物具有什么样的行为

大驼峰命名法

  • 每一个单词首字母大写
  • 单词与单词间没有下划线
类名的确定

名词提炼法分析整个业务流程,出现的名词,通常就是找到的类

属性和方法的确定
  • 对象的特征描述,通常可以定义成属性
  • 对象具有的行为(动词),通常可以定义成方法

需求中没有涉及的属性或方法在设计类时不需要考虑

1.3 面向对象基础语法

  • 使用内置函数 dir 传入标识符/数据可以查看对象内的所有属性和方法
  • __方法名__ 格式的方法是 Python 提供的内置方法和属性
方法名类型作用
_new_方法创建对象时,会被自动调用
_init_方法对象初始化时,会被自动调用
_del_方法对象被从内存中销毁前,会被自动调用
_str_方法返回对象的描述信息,print 函数输出使用,必须返回一个字符串

定义简单的类(只包含方法)

1
2
3
4
5
class 类名
        def 方法1(self, 参数列表):
            pass
        def 方法2(self, 参数列表):
            pass
  • 方法的定义格式和函数几乎一样
    • 区别在于第一个参数必须是 self

创建对象

  • 当一个类定义完成后,要使用这个类创建对象
1
对象变量 = 类名()

引用的概念

面向对象中引用同样适用

  • 在 Python 中使用类创建对象后,变量仍然记录的是对象在内存中的地址
  • 也就是对象变量引用了新建的猫对象
  • 使用 print 输出对象变量,默认情况下能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址

方法中的 self 参数

  • Python 中给对象设置属性非常容易,只需要在类的外部的代码中直接通过 . 设置一个属性即可

提示:

  • 虽然简单,但不推荐使用

  • 因为对象属性的封装应该封装在类的内部

  • 如果运行时没有找到属性,程序会报错

1
2
tom.name = "Tom"
# 利用赋值语句,给 tom 对象设置 name 属性为Tom
  • 在类封装的方法内部,self 表示当前调用方法的对象自己
    • 也就是说:哪一个对象调用的方法,self 就是哪一个对象的引用
  • 调用方法时程序员不需要传递 self 参数
  • 在方法内部:
    • 可以通过 self.访问对象属性
    • 也可以通过 self.调用对象的其他方法

初始化方法

  • 当使用 类名() 创建对象时,会自动执行以下操作:
    • 为对象在内存中分配空间–创建对象
    • 为对象的属性设置初始值--初始化方法(init)
  • 这个初始化方法就是 __init__ 方法,这是对象的内置方法

__init__ 方法是专门用来定义一个类具有哪些属性的方法

1
2
3
4
5
6
7
8
9
class Cat:
    def __init__(self):
        print("初始化方法")


# 使用 类名() 创建对象的时候,会自动调用初始化方法 __init__


tom = Cat()

在初始化方法内部定义属性

  • __init__ 方法内部使用 self.属性名 = 形参 就可以定义属性

  • 定义属性后,再使用类创建的对象都会拥有该属性

  • 创建对象时,使用 类名(属性1, 属性2...) 调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Cat:
    def __init__(self, new_name):
        print("初始化方法")
        # self.name = "Tom"
        self.name = new_name

    def eat(self):
        print("%s爱吃鱼" % self.name)


# 使用 类名() 创建对象的时候,会自动调用初始化方法 __init__


tom = Cat("Tom")
print(tom.name)

1.4 案例

小明爱跑步

需求

  1. 小明体重 75.0 公斤
  2. 小明每次跑步会减肥 0.5 公斤
  3. 小明每次吃东西体重增加 1 公斤
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person:
    def __init__(self, name, weight):
        # self.属性 = 形参
        self.name = name
        self.weight = weight


    def __str__(self):

        return "我的名字是 %s,体重是 %d.2 公斤" % (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.0

xiaoming = Person("小明",75.0 )
xiaoming.run()
xiaoming.eat()
print(xiaoming)
  • 在对象的方法内部,可以直接访问对象的属性
  • 同一个类创建的多个对象之间,属性互不干扰

摆放家具

  1. 房子有户型、总面积和家具名称列表
    • 新房子没有任何家具
  2. 家具有名字和占地面积,其中:
    • 床占地 4 平米
    • 衣柜占地 2 平米
    • 餐桌占地 1.5 平米
  3. 将以上三件家具添加到房子中
  4. 打印房子时,要求输出:
    • 户型
    • 总面积
    • 剩余面积
    • 家具名称列表
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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))

    def add_item(self, item):
        print("要添加 %s" % item)
        # 1. 判断家具的面积
        if item.area > self.free_area:
            print("%s面积太大,无法添加" % item.name)
            return

        # 2. 将家具的名称添加到列表
        self.item_list.append(item.name)

        # 3. 计算剩余面积
        self.free_area -= item.area


# 1. 创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed, chest, table)
# 2. 创建房子变量
my_home = House("两室一厅", 60)

my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)

封装案例

封装

  • 封装是面向对象编程的一大特点
  • 面向对象编程的第一步–将属性和方法封装到一个抽象的类中
  • 外界使用类创建对象,让对象调用方法
  • 对象调用方法的细节都被封装在类的内部

一个对象的属性可以是另外一个类创建的对象

需求:

  1. 士兵许三多有一把 AK47
  2. 士兵可以开火
  3. 枪能发射子弹
  4. 枪能填充子弹

定义属性的初始值

  • 如果不知道设置什么初始值,可以设置为 None
  • None 关键字表示什么也没有
  • 表示一个空对象,没有方法和属性,是一个特殊的常量
  • 可以将 None 赋值给任何一个变量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Gun:
    def __init__(self, model):
        # 1. 枪的型号
        self.model = model
        # 2. 子弹的数量
        self.bullet_count = 0

    def add_bullet(self, count):
        self.bullet_count += count

    def shoot(self):
        # 1. 判断子弹数量
        if self.bullet_count <= 1:
            print("[%s]没有子弹了" % self.model)
            return
        # 2. 发射子弹 -1
        self.bullet_count -= 1
        # 3. 提示发射信息
        print("[%s] 突突突...[%d]" % (self.model, self.bullet_count))



class Soldier:
    def __init__(self, name):
        # 1. 新兵的姓名
        self.name = name
        # 2. 枪 新兵没有枪
        self.gun = None

    def fire(self):
        # 1. 判断士兵是否有枪
        if self.gun is None:
            print("[%s] 还没有枪" % self.name)
            return

        # 2. 高喊口号
        print("冲啊[%s]" % self.name)
        # 3. 装填子弹
        self.gun.add_bullet(50)
        # 4. 发射子弹
        self.gun.shoot()

# 1. 创建枪对象
ak47 = Gun("AK47")
# 2. 创建许三多
xusanduo = Soldier("许三多")

xusanduo.gun = ak47
xusanduo.fire()
print(xusanduo.gun)

身份运算符

  • 身份运算符是用于比较两个对象的内存地址是否一致–是否是对同一个对象的引用
  • 在 Python 中,针对 None 比较时,建议使用 is 判断
运算符描述实例
isis 是判断两个标识符是不是引用同一个对象x is y, 类似 id(x) == id(y)
is notis not 是判断两个标识符是不是引用不同对象x is not y, 类似 id(a)!id(b)

is== 区别:

  • is 是判断两个变量是不是引用同一个对象
  • == 判断引用变量的值是否相等

1.5 私有属性和私有方法

  • 对象的某些属性或方法可能只希望在对象的内部被使用,不希望在外部被访问到
  • 私有属性就是对象不希望公开的属性
  • 私有方法就是对象不希望公开的方法

定义方式

  • 在定义属性或方法时,在属性名或方法名前增加两个下划线

伪私有属性和私有方法

Python 中并没有真正意义的私有

  • 在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问
  • 处理方式:在名称前加上 类名 => _类名__名称

不要使用这种方法访问私有属性和私有方法


2.继承和多态

2.1 单继承

继承的概念及语法

**概念: **子类拥有父类的所有方法和属性

  • 实现代码的重用
  • 设计类的技巧
  • 子类针对自己特有的需求,编写特定代码

语法:

1
2
class 类名(父类名):
    pass
  • 类似层次网络模型

  • 子类/派生类—-父类/基类

  • 子类拥有父类以及父类的父类的所有属性和方法

方法的重写

重写(override)

当父类的方法不能满足子类需求,可以对方法进行重写

  1. 覆盖父类的方法
  • 父类的方法实现和子类的方法实现完全不同

  • 在子类中重新定义和父类同名的方法

    • 运行时只会调用子类中重写的方法
  1. 对父类方法进行扩展

    • 子类的方法实现包含父类的方法实现
    • 在子类中重写父类的方法
    • 在需要的位置使用 super.()父类方法 , 调用父类方法的执行
    • 代码其他位置针对子类的需求,编写子类特有的实现

super

  • 在 Python 中, super 是一个特殊的类
  • super() 就是 super 类创建的对象
  • 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法

父类的私有属性和私有方法

  • 子类对象不能在自己内部直接访问父类的私有属性私有方法
  • 父类的公有方法可以访问父类的私有属性和私有方法
  • 子类可以以借助父类的公有方法间接访问父类的私有属性和私有方法

2.2 多继承

  • 子类可以拥有多个父类,并且具有所有父类的属性和方法
  • 如果父类之间存在同名的属性和方法, 应尽量避免使用多继承

语法:

1
2
class 子类名(父类名1, 父类名2,...)
    pass

Python 中的MRO–方法搜索顺序

  • 针对类提供了内置属性__mro__ , 可以查看方法搜索顺序
  • MROmethod resolution order 的缩写,主要用于在多继承时判断方法和属性的调用路径

**新式类和旧式类: **

object 是 Python 为所有对象提供的基类, 提供有一些内置的属性和方法

  • 新式类: 以 object 为基类的类, 推荐使用
  • 经典类: 不以 object 为基类的类, 不推荐使用

为保证代码可以同时在两个版本运行, 如果没有父类, 建议统一继承自 object

2.3 多态

**概念:**不同的子类对象调用相同的父类方法

  • 继承重写父类方法为前提
  • 是调用方法的技巧, 不会影响类的内部设计

案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Dog(object):

    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(object):

    def __init__(self, name):
        self.name = name

    def game_with_dog(self, dog):
        print("%s%s 快乐的玩耍" % (self.name, dog.name))
        dog.game()


# 1. 创建狗对象
# wangcai = Dog("旺财")
wangcai = XiaoTianQuan("飞天旺财")
# 2. 创建小明对象
xiaoming = Person("小明")
# 3. 让小明和狗玩耍
xiaoming.game_with_dog(wangcai)
# 传入不同的狗参数即可

3. 类属性

3.1 类的结构

类是一个特殊的对象

Python 中一切皆对象

  • class AAA: 定义的对象属于类对象

  • obj1 = AAA() 属于实例对象

程序运行时,类对象在内存中只有一份。

3.2 类属性和实例属性

概念和使用

  • 类属性就是给类对象定义的属性
  • 记录与类相关的特征
  • 类属性不会用于记录具体对象的的特征
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Tool(object):
    # 使用赋值语句即可定义类属性
    count = 0
    def __init__(self, name):
        self.name = name
        Tool.count += 1

tool1 = Tool("斧头")
tool2 = Tool("榔头")


print(Tool.count)

3.3 类方法和静态方法

类方法

类属性

  • 类属性是针对类对象定义的属性
    • 使用赋值语句在 class 关键字下方可以定义类属性
    • 类属性用于记录与这个类相关的特征
  • 类属性查找存在向上查找方式

类方法

  • 针对类对象创建的方法
    • 类方法内部可以直接访问类属性或者调用其他的类方法

语法如下:

1
2
3
@classmethod
def 类方法名(cls):
    pass
  • 需要用修饰器@classmethod标识,告诉解释器这是一个类方法
  • 类方法第一个参数应该是 cls
    • 在类方法内部可使用cls.属性访问当前类属性

静态方法

在类中封封装方法,这个方法:

  • 既不需要访问实例属性或者调用实例方法
  • 也不需要访问类属性或者调用类方法

语法:

1
2
3
@staticmethod
def 静态方法名():
    pass
  • 通过类名.调用静态方法

3.4 方法综合案例

需求:

  1. 设计一个 Game
  2. 属性:
    • 类属性: top_score记录游戏历史最高分
    • 实例属性:player_name记录当前游戏玩家姓名
  3. 方法:
    • 静态方法:show_help显示游戏帮助信息
    • 类方法:show_top_score显示历史最高分
    • 实例方法:start_game开始当前玩家的游戏
  4. 主程序步骤
    1. 查看帮助信息
    2. 查看历史最高分
    3. 创建游戏对象,开始游戏

小结

实例方法方法内部需要访问实例属性,实例方法内部可以使用类名.访问类属性
类方法方法内部只需要访问类属性
静态方法方法内部不需要访问实例属性和类属性