锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

python学习笔记:面向对象

时间:2023-05-18 20:07:00 二极管atv02w220b

第三部分:面向对象

# 基本语法面对象

## 目标

* `dir` 内置函数
* 定义简单的类别(只包括方法)
* 方法中的 `self` 参数
* 初始化方法
* 内置方法和属性

## 01. `dir` 内置函数(知道)

* 在 `Python` 中 **对象几乎无处不在。**,我们以前学过 **变量**、**数据**、**函数** 都是对象

在 `Python` 以下两种方法可用于验证:

1. 在 **标识符** / **数据** 后输入一个 `.`,然后按下 `TAB` 键,`iPython` 提示对象可以调用 **方法列表**
2. 使用内置函数 `dir` 传入 **标识符** / **数据**,查看对象内的 **所有属性和方法**

**提示** `__方法名__` 格式的方法是 `Python` 提供的 **内置方法 / 属性**,以后会给大家介绍一些常用的。 内置方法 / 属性

| 序号 | 方法名 | 类型 | 作用 |
| :--: | :--------: | :--: | -------------------------------------------- |
| 01 | `__new__` | 方法 | **创建对象**时,会被 **自动** 调用 |
| 02 | `__init__` | 方法 | **对象初始化**时,会被 **自动** 调用 |
| 03 | `__del__` | 方法 | **从内存中销毁对象**前,会被 **自动** 调用 |
| 04 | `__str__` | 方法 | 返回**对象的描述信息**,`print` 函数输出使用 |

**提示** 利用好 `dir()` 函数,很多内容在学习时不需要死记硬背

## 02. 定义简单的类别(只包括方法)

> **面向对象** 是 **更大** 的 **封装**,在 **一个类中 封装 多个方法**,这样 **这些方法可以通过这一类创建的对象直接调用**!

### 2.1 定义只包含方法类

* 在 `Python` 语法格式如下:

```python
class 类名:

def 方法1(self, 参数列表:
pass

def 方法2(self, 参数列表:
pass
```

* **方法** 定义格式和我以前学过的定义格式**函数** 几乎一样
* 区别在于第一个参数必须是 `self`,暂时记住,稍后介绍 `self`

> 注意:**类名** 的 命名规则 要符合 **大驼峰命名法**

### 2.2 创建对象

* 语法格式如下:

```python
对象变量 = 类名()
```

### 2.3 第一个面向对象程序

**需求**

* **小猫** 爱 **吃** 鱼,**小猫** 要 **喝** 水

**分析**

1. 定义猫 `Cat`
2. 定义两种方法 `eat` 和 `drink`
3. 按照需求 —— 属性不需要定义

![004_Cat类1-w134](media/15006092456994/004_Cat类1.png)

```python
class Cat:
"""这是猫"""

def eat(self):
print("小猫爱吃鱼")

def drink(self):
print("小猫在喝水")

tom = Cat()
tom.drink()
tom.eat()
```

#### 引用概念的强调

> 在面向对象开发中,**引用**概念也适用!

* 在 `Python` 中使用类 **创建对象后**,`tom` 变量中 还记录的是 **内存中对象的地址**
* 也就是 `tom` 变量 **引用** 了 **新建对象**
* 使用 `print` 输出 **对象变量**,默认情况下,可以输出此变量 **引用的对象** 是 **由哪个类创建的对象**,以及 **内存中的地址**(**十六进制表示**)

> 提示:通常用于计算机 **十六进制** 表示 **内存地址**
>
> * **十进制** 和 **十六进制** 它们都用来表达数字,但表达方式不同
> * **十进制** 和 **十六进制** 的数字之间可以来回转换

* `%d` 可以以 **10 进制** 输出数字
* `%x` 可以以 **16 进制** 输出数字

#### 案例进阶 —— 使用 Cat 类创建另一个对象

```python
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
```

> 提问:`tom` 和 `lazy_cat` 是同一个对象吗?

## 03. 方法中的 `self` 参数

### 3.1 案例改造 —— 增加对象的属性

* 在 `Python` 中,要 **为对象设置属性**,很容易,**但不建议使用**
* 因为:物体属性的包装应该包装在类别的内部
* 只需要在 **类的外部代码** 中直接通过 `.` 设置属性

> 注:这种方法虽然简单,但不推荐!

```python
tom.name = "Tom"
...

lazy_cat.name = "大懒猫"
```

### 3.2 使用 `self` 在方法内输出每只猫的名字

> 由 **哪一个对象** 调用方法,方法内部 `self` 就是 **引用哪个对象**

* 在类别方法内部,`self` 就表示 **目前调用该方法的对象本身**
* **调用方法时**,程序员不需要传输 `self` 参数
* **在方法内部**
* 可以通过 `self.` **访问对象的属性**
* 也可以通过 `self.` **调用其它对象方法**
* 改造代码如下:

```python
class Cat:

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

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

lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat()
```

![005_方法中的self-w625](media/15006092456994/005_方法中的self.png)

* 在 **类的外部**,通过 `变量名.` 访问对象的 **属性和方法**
* 在 **类包装方法中**,通过 `self.` 访问对象的 **属性和方法**

## 04. 初始化方法

### 4.1 以前的代码问题 —— 对象在类外部增加属性

* 将案例代码进行调整,**先调用方法 再设置属性**,观察执行效果

```python
tom = Cat()
tom.drink()
tom.eat()
tom.name = "Tom"
print(tom)
```

* 程序执行报错如下:

```
AttributeError: 'Cat' object has no attribute 'name'
属性错误:Cat' 对象没有 'name' 属性
```

**提示**

* 在日常开发中,不推荐 **类的外部** 增加对象的属性
* 如果**如果在运行过程中找不到属性,程序就会报错**
* 对象应包含哪些属性? **包装在类**

### 4.2 初始化方法

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

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

在 `Cat` 中增加 `__init__` 方法,验证该方法在创建对象时会被自动调用

```python
class Cat:
    """这是一个猫类"""

    def __init__(self):
        print("初始化方法")
```

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

* 在 `__init__` 方法内部使用 `self.属性名 = 属性的初始值` 就可以 **定义属性**
* 定义属性之后,再使用 `Cat` 类创建的对象,都会拥有该属性

```python
class Cat:

    def __init__(self):

        print("这是一个初始化方法")
        
        # 定义用 Cat 类创建的猫对象都有一个 name 的属性
        self.name = "Tom"

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

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

tom.eat()

```

### 4.4 改造初始化方法 —— 初始化的同时设置初始值

* 在开发中,如果希望在 **创建对象的同时,就设置对象的属性**,可以对 `__init__` 方法进行 **改造**
  1. 把希望设置的属性值,定义成 `__init__` 方法的参数
  2. 在方法内部使用 `self.属性 = 形参` 接收外部传递的参数
  3. 在创建对象时,使用 `类名(属性1, 属性2...)` 调用

```python
class Cat:

    def __init__(self, name):
        print("初始化方法 %s" % name)
        self.name = name
    ...
    
tom = Cat("Tom")
...

lazy_cat = Cat("大懒猫")
...
```

## 05. 内置方法和属性

| 序号 |  方法名   | 类型 | 作用                                         |
| :--: | :-------: | :--: | -------------------------------------------- |
|  01  | `__del__` | 方法 | **对象被从内存中销毁**前,会被 **自动** 调用 |
|  02  | `__str__` | 方法 | 返回**对象的描述信息**,`print` 函数输出使用 |

### 5.1 `__del__` 方法(知道)

* 在 `Python` 中
  * 当使用 `类名()` 创建对象时,为对象 **分配完空间**后,**自动** 调用 `__init__` 方法
  * 当一个 **对象被从内存中销毁** 前,会 **自动** 调用 `__del__` 方法

* **应用场景**
  * `__init__` 改造初始化方法,可以让创建对象更加灵活
  * `__del__` 如果希望在对象被销毁前,再做一些事情,可以考虑一下 `__del__` 方法

* **生命周期**
  * 一个对象从调用 `类名()` 创建,生命周期开始
  * 一个对象的 `__del__` 方法一旦被调用,生命周期结束
  * 在对象的生命周期内,可以访问对象属性,或者让对象调用方法

```python
class Cat:

    def __init__(self, new_name):

        self.name = new_name

        print("%s 来了" % self.name)

    def __del__(self):

        print("%s 去了" % self.name)

# tom 是一个全局变量
tom = Cat("Tom")
print(tom.name)

# del 关键字可以删除一个对象
del tom

print("-" * 50)

```

### 5.2 `__str__` 方法

* 在 `Python` 中,使用 `print` 输出 **对象变量**,默认情况下,会输出这个变量 **引用的对象** 是 **由哪一个类创建的对象**,以及 **在内存中的地址**(**十六进制表示**)
* 如果在开发中,希望使用 `print` 输出 **对象变量** 时,能够打印 **自定义的内容**,就可以利用 `__str__` 这个内置方法了

> 注意:`__str__` 方法必须返回一个字符串

```python
class Cat:

    def __init__(self, new_name):

        self.name = new_name

        print("%s 来了" % self.name)

    def __del__(self):

        print("%s 去了" % self.name)

    def __str__(self):
        return "我是小猫:%s" % self.name

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

```
====

补充知识:
* 在 `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
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a 
False
>>> b == a
True
```

====

# 私有属性和私有方法

## 01. 应用场景及定义方式

**应用场景**

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

**定义方式**

* 在 **定义属性或方法时**,在 **属性名或者方法名前** 增加 **两个下划线**,定义的就是 **私有** 属性或方法

![010_私有属性和方法-w265](media/15006305884400/010_%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95.png)

```python
class Women:

    def __init__(self, name):

        self.name = name
        # 不要问女生的年龄
        self.__age = 18

    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiaofang = Women("小芳")
# 私有属性,外部不能直接访问
# print(xiaofang.__age)

# 私有方法,外部不能直接调用
# xiaofang.__secret()

```

## 02. 伪私有属性和私有方法(科普)

> 提示:在日常开发中,**不要使用这种方式**,**访问对象的 私有属性 或 私有方法**

`Python` 中,并没有 **真正意义** 的 **私有**

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

```python
# 私有属性,外部不能直接访问到
print(xiaofang._Women__age)

# 私有方法,外部不能直接调用
xiaofang._Women__secret()

```

====


面向对象之:单例
# 单例

## 目标

* 单例设计模式
* `__new__` 方法
* Python 中的单例

## 01. 单例设计模式

* 设计模式
  * **设计模式** 是 **前人工作的总结和提炼**,通常,被人们广泛流传的设计模式都是针对 **某一特定问题** 的成熟的解决方案
  * 使用 **设计模式** 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

* 单例设计模式
  * **目的** —— 让 **类** 创建的对象,在系统中 **只有** **唯一的一个实例**
  * 每一次执行 `类名()` 返回的对象,**内存地址是相同的**

### 单例设计模式的应用场景

* **音乐播放** 对象
* **回收站** 对象
* **打印机** 对象
* ……

## 02. `__new__` 方法

* 使用 **类名()** 创建对象时,`Python` 的解释器 **首先** 会 调用 `__new__` 方法为对象 **分配空间**
* `__new__` 是一个 由 `object` 基类提供的 **内置的静态方法**,主要作用有两个:
  * 1) 在内存中为对象 **分配空间**
  * 2) **返回** 对象的引用
* `Python` 的解释器获得对象的 **引用** 后,将引用作为 **第一个参数**,传递给 `__init__` 方法

> 重写 `__new__` 方法 的代码非常固定!

* 重写 `__new__` 方法 **一定要** `return super().__new__(cls)` 
* 否则 Python 的解释器 **得不到** 分配了空间的 **对象引用**,**就不会调用对象的初始化方法**
* 注意:`__new__` 是一个静态方法,在调用时需要 **主动传递** `cls` 参数

![022_对象分配空间和初始化-w838](media/15016413216376/022_%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E7%A9%BA%E9%97%B4%E5%92%8C%E5%88%9D%E5%A7%8B%E5%8C%96.png)

**示例代码**

```python
class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        # 如果不返回任何结果,
        return super().__new__(cls)

    def __init__(self):
        print("初始化音乐播放对象")

player = MusicPlayer()

print(player)

```

## 03. Python 中的单例

* **单例** —— 让 **类** 创建的对象,在系统中 **只有** **唯一的一个实例**
  1. 定义一个 **类属性**,初始值是 `None`,用于记录 **单例对象的引用**
  2. 重写 `__new__` 方法
  3. 如果 **类属性** `is None`,调用父类方法分配空间,并在类属性中记录结果
  4. 返回 **类属性** 中记录的 **对象引用**

![023_单例流程-w893](media/15016413216376/023_%E5%8D%95%E4%BE%8B%E6%B5%81%E7%A8%8B.png)


```python
class MusicPlayer(object):

    # 定义类属性记录单例对象引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance = super().__new__(cls)

        # 2. 返回类属性的单例引用
        return cls.instance

```

### 只执行一次初始化工作

* 在每次使用 `类名()` 创建对象时,`Python` 的解释器都会自动调用两个方法:
  * `__new__` 分配空间
  * `__init__` 对象初始化
* 在上一小节对 `__new__` 方法改造之后,每次都会得到 **第一次被创建对象的引用**
* 但是:**初始化方法还会被再次调用**

**需求**

* 让 **初始化动作** 只被 **执行一次**

**解决办法**

1. 定义一个类属性 `init_flag` 标记是否 **执行过初始化动作**,初始值为 `False`
2. 在 `__init__` 方法中,判断 `init_flag`,如果为 `False` 就执行初始化动作
3. 然后将 `init_flag` 设置为 `True`
4. 这样,再次 **自动** 调用 `__init__` 方法时,**初始化动作就不会被再次执行** 了

```python
class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None
    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否是空对象
        if cls.instance is None:
            # 2. 调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3. 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):

        if not MusicPlayer.init_flag:
            print("初始化音乐播放器")

            MusicPlayer.init_flag = True


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

```
====

面向对象之:多态

# 多态

## 目标

* 多态

**面向对象三大特性**

1. **封装** 根据 **职责** 将 **属性** 和 **方法** **封装** 到一个抽象的 **类** 中

   * 定义类的准则 

2. **继承** **实现代码的重用**,相同的代码不需要重复的编写

   * 设计类的技巧 
   * 子类针对自己特有的需求,编写特定的代码

3. **多态** 不同的 **子类对象** 调用相同的 **父类方法**,产生不同的执行结果

   * **多态** 可以 **增加代码的灵活度**
   * 以 **继承** 和 **重写父类方法** 为前提
   * 是调用方法的技巧,**不会影响到类的内部设计**

   ![016_多态示意图-w384](media/15012536156932/016_%E5%A4%9A%E6%80%81%E7%A4%BA%E6%84%8F%E5%9B%BE.png)

## 多态案例演练

**需求**

1. 在 `Dog` 类中封装方法 `game`
   * 普通狗只是简单的玩耍
2. 定义 `XiaoTianDog` 继承自 `Dog`,并且重写 `game` 方法
   * 哮天犬需要在天上玩耍
3. 定义 `Person` 类,并且封装一个 **和狗玩** 的方法
   * 在方法内部,直接让 **狗对象** 调用 `game` 方法

![016_多态-w701](media/15012536156932/016_%E5%A4%9A%E6%80%81.png)

**案例小结**

* `Person` 类中只需要让 **狗对象** 调用 `game` 方法,而不关心具体是 **什么狗**
  * `game` 方法是在 `Dog` 父类中定义的
* 在程序执行时,传入不同的 **狗对象** 实参,就会产生不同的执行效果

> **多态** 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!

```python
class Dog(object):

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

    def game(self):
        print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(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 = XiaoTianDog("飞天旺财")

# 2. 创建一个小明对象
xiaoming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
        
```

====

面向对象之:类属性和类方法

# 类属性和类方法

## 目标

* 类的结构
* 类属性和实例属性
* 类方法和静态方法

## 01. 类的结构

### 1.1 术语 —— 实例

1. 使用面相对象开发,**第 1 步** 是设计 **类**
2. 使用 **类名()** 创建对象,**创建对象** 的动作有两步:
   * 1) 在内存中为对象 **分配空间**
   * 2) 调用初始化方法 `__init__` 为 **对象初始化**
3. 对象创建后,**内存** 中就有了一个对象的 **实实在在** 的存在 —— **实例**

![017_类的结构示意图I-w473](media/15016413127744/017_%E7%B1%BB%E7%9A%84%E7%BB%93%E6%9E%84%E7%A4%BA%E6%84%8F%E5%9B%BEI.png)

因此,通常也会把:

1. 创建出来的 **对象** 叫做 **类** 的 **实例**
2. 创建对象的 **动作** 叫做 **实例化**
3. **对象的属性** 叫做 **实例属性**
4. **对象调用的方法** 叫做 **实例方法**

在程序执行时:

1. 对象各自拥有自己的 **实例属性**
2. 调用对象方法,可以通过 `self.` 
   * 访问自己的属性
   * 调用自己的方法

**结论**

* **每一个对象** 都有自己 **独立的内存空间**,**保存各自不同的属性**
* **多个对象的方法**,**在内存中只有一份**,在调用方法时,**需要把对象的引用** 传递到方法内部

### 1.2 类是一个特殊的对象

> `Python` 中 **一切皆对象**:
>
> * `class AAA:` 定义的类属于 **类对象**
> * `obj1 = AAA()` 属于 **实例对象**

* 在程序运行时,**类** 同样 **会被加载到内存**
* 在 `Python` 中,**类** 是一个特殊的对象 —— **类对象**
* 在程序运行时,**类对象** 在内存中 **只有一份**,使用 **一个类** 可以创建出 **很多个对象实例**
* 除了封装 **实例** 的 **属性** 和 **方法**外,**类对象** 还可以拥有自己的 **属性** 和 **方法**
  1. **类属性**
  2. **类方法**
* 通过 **类名.** 的方式可以 **访问类的属性** 或者 **调用类的方法**

![017_类的结构示意图II-w545](media/15016413127744/017_%E7%B1%BB%E7%9A%84%E7%BB%93%E6%9E%84%E7%A4%BA%E6%84%8F%E5%9B%BEII.png)

## 02. 类属性和实例属性

### 2.1 概念和使用

* **类属性** 就是给 **类对象** 中定义的 **属性**
* 通常用来记录 **与这个类相关** 的特征
* **类属性** **不会用于**记录 **具体对象的特征**

**示例需求**

* 定义一个 **工具类**
* 每件工具都有自己的 `name`
* **需求** —— 知道使用这个类,创建了多少个工具对象?

![018_类属性案例I-w263](media/15016413127744/018_%E7%B1%BB%E5%B1%9E%E6%80%A7%E6%A1%88%E4%BE%8BI.png)

```python
class Tool(object):

    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

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

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)

```

### 2.2 属性的获取机制(科普)

* 在 `Python` 中 **属性的获取** 存在一个 **向上查找机制**

![019_通过对象访问类属性-w779](media/15016413127744/019_%E9%80%9A%E8%BF%87%E5%AF%B9%E8%B1%A1%E8%AE%BF%E9%97%AE%E7%B1%BB%E5%B1%9E%E6%80%A7.png)

* 因此,要访问类属性有两种方式:
  1. **类名.类属性**
  2. **对象.类属性** (不推荐)

**注意**

* 如果使用 `对象.类属性 = 值` 赋值语句,只会 **给对象添加一个属性**,而不会影响到 **类属性的值**

## 03. 类方法和静态方法

### 3.1 类方法

* **类属性** 就是针对 **类对象** 定义的属性
  * 使用 **赋值语句** 在 `class` 关键字下方可以定义 **类属性**
  * **类属性** 用于记录 **与这个类相关** 的特征
* **类方法** 就是针对 **类对象** 定义的方法
  * 在 **类方法** 内部可以直接访问 **类属性** 或者调用其他的 **类方法**

**语法如下**

```python
@classmethod
def 类方法名(cls):
    pass
```

* 类方法需要用 **修饰器** `@classmethod` 来标识,**告诉解释器这是一个类方法**
* 类方法的 **第一个参数** 应该是 `cls`
  * 由 **哪一个类** 调用的方法,方法内的 `cls` 就是 **哪一个类的引用**
  * 这个参数和 **实例方法** 的第一个参数是 `self` 类似
  * **提示** 使用其他名称也可以,不过习惯使用 `cls`

3. 通过 **类名.** 调用 **类方法**,**调用方法时**,不需要传递 `cls` 参数
4. **在方法内部**
   * 可以通过 `cls.` **访问类的属性**
   * 也可以通过 `cls.` **调用其他的类方法**

**示例需求**

* 定义一个 **工具类**
* 每件工具都有自己的 `name`
* **需求** —— 在 **类** 封装一个 `show_tool_count` 的类方法,输出使用当前这个类,创建的对象个数

![020_类方法案例-w263](media/15016413127744/020_%E7%B1%BB%E6%96%B9%E6%B3%95%E6%A1%88%E4%BE%8B.png)

```python
@classmethod
def show_tool_count(cls):
    """显示工具对象的总数"""
    print("工具对象的总数 %d" % cls.count)
```

> 在类方法内部,可以直接使用 `cls` 访问 **类属性** 或者 **调用类方法**

### 3.2 静态方法

* 在开发时,如果需要在 **类** 中封装一个方法,这个方法:
  * 既 **不需要** 访问 **实例属性** 或者调用 **实例方法**
  * 也 **不需要** 访问 **类属性** 或者调用 **类方法**

* 这个时候,可以把这个方法封装成一个 **静态方法**

**语法如下**

```python
@staticmethod
def 静态方法名():
    pass
```

* **静态方法** 需要用 **修饰器** `@staticmethod` 来标识,**告诉解释器这是一个静态方法**
* 通过 **类名.** 调用 **静态方法**

```python
class Dog(object):
    
    # 狗对象计数
    dog_count = 0
    
    @staticmethod
    def run():
        
        # 不需要访问实例属性也不需要访问类属性的方法
        print("狗在跑...")

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

### 3.3 方法综合案例

**需求**

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

![021_方法综合案例-w351](media/15016413127744/021_%E6%96%B9%E6%B3%95%E7%BB%BC%E5%90%88%E6%A1%88%E4%BE%8B.png)

#### 案例小结

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

**提问**

如果方法内部 即需要访问 **实例属性**,又需要访问 **类属性**,应该定义成什么方法?

**答案**

* 应该定义 **实例方法**
* 因为,**类只有一个**,在 **实例方法** 内部可以使用 **类名.** 访问类属性

```python
class Game(object):

    # 游戏最高分,类属性
    top_score = 0

    @staticmethod
    def show_help():
        print("帮助信息:让僵尸走进房间")
        
    @classmethod
    def show_top_score(cls):
        print("游戏最高分是 %d" % cls.top_score)

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

    def start_game(self):
        print("[%s] 开始游戏..." % self.player_name)
        
        # 使用类名.修改历史最高分
        Game.top_score = 999

# 1. 查看游戏帮助
Game.show_help()

# 2. 查看游戏最高分
Game.show_top_score()

# 3. 创建游戏对象,开始游戏
game = Game("小明")

game.start_game()

# 4. 游戏结束,查看游戏最高分
Game.show_top_score()

```


====

面向对象之:继承

# 继承

## 目标

* 单继承
* 多继承

**面向对象三大特性**

1. **封装** 根据 **职责** 将 **属性** 和 **方法** **封装** 到一个抽象的 **类** 中
2. **继承** **实现代码的重用**,相同的代码不需要重复的编写
3. **多态** 不同的对象调用相同的方法,产生不同的执行结果,**增加代码的灵活度**

## 01. 单继承

### 1.1 继承的概念、语法和特点

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

![011_继承对比图示](media/15006307099794/011_%E7%BB%A7%E6%89%BF%E5%AF%B9%E6%AF%94%E5%9B%BE%E7%A4%BA.png)

#### 1) 继承的语法

```python
class 类名(父类名):

    pass
```

* **子类** 继承自 **父类**,可以直接 **享受** 父类中已经封装好的方法,不需要再次开发
* **子类** 中应该根据 **职责**,封装 **子类特有的** **属性和方法**

#### 2) 专业术语

* `Dog` 类是 `Animal` 类的**子类**,`Animal` 类是 `Dog` 类的**父类**,`Dog` 类从 `Animal` 类**继承**
* `Dog` 类是 `Animal` 类的**派生类**,`Animal` 类是 `Dog` 类的**基类**,`Dog` 类从 `Animal` 类**派生**

#### 3) 继承的传递性

* `C` 类从 `B` 类继承,`B` 类又从 `A` 类继承
* 那么 `C` 类就具有 `B` 类和 `A` 类的所有属性和方法

**子类** 拥有 **父类** 以及 **父类的父类** 中封装的所有 **属性** 和 **方法**

**提问**

**哮天犬** 能够调用 `Cat` 类中定义的 `catch` 方法吗?

**答案**

**不能**,因为 **哮天犬** 和 `Cat` 之间没有 **继承** 关系

### 1.2 方法的重写

* **子类** 拥有 **父类** 的所有 **方法** 和 **属性**
* **子类** 继承自 **父类**,可以直接 **享受** 父类中已经封装好的方法,不需要再次开发

**应用场景**

* 当 **父类** 的方法实现不能满足子类需求时,可以对方法进行 **重写(override)**

![012_继承方法的重写-w203](media/15006307099794/012_%E7%BB%A7%E6%89%BF%E6%96%B9%E6%B3%95%E7%9A%84%E9%87%8D%E5%86%99.png)

**重写** 父类方法有两种情况:

1. **覆盖** 父类的方法
2. 对父类方法进行 **扩展**

#### 1) 覆盖父类的方法

* 如果在开发中,**父类的方法实现** 和 **子类的方法实现**,**完全不同**
* 就可以使用 **覆盖** 的方式,**在子类中** **重新编写** 父类的方法实现

> 具体的实现方式,就相当于在 **子类中** 定义了一个 **和父类同名的方法并且实现**

重写之后,在运行时,**只会调用** 子类中重写的方法,而不再会调用 **父类封装的方法**

#### 2) 对父类方法进行 **扩展**

* 如果在开发中,**子类的方法实现** 中 **包含** **父类的方法实现**
  * **父类原本封装的方法实现** 是 **子类方法的一部分**
* 就可以使用 **扩展** 的方式
  1. **在子类中** **重写** 父类的方法
  2. 在需要的位置使用 `super().父类方法` 来调用父类方法的执行
  3. 代码其他的位置针对子类的需求,编写 **子类特有的代码实现**

##### 关于 `super`

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

##### 调用父类方法的另外一种方式(知道)

> 在 `Python 2.x` 时,如果需要调用父类的方法,还可以使用以下方式:

```python
父类名.方法(self)
```

* 这种方式,目前在 `Python 3.x` 还支持这种方式
* 这种方法 **不推荐使用**,因为一旦 **父类发生变化**,方法调用位置的 **类名** 同样需要修改

**提示**

* 在开发时,`父类名` 和 `super()` 两种方式不要混用
* 如果使用 **当前子类名** 调用方法,会形成递归调用,**出现死循环**

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

1. **子类对象** **不能** 在自己的方法内部,**直接** 访问 父类的 **私有属性** 或 **私有方法**
2. **子类对象** 可以通过 **父类** 的 **公有方法** **间接** 访问到 **私有属性** 或 **私有方法**

> * **私有属性、方法** 是对象的隐私,不对外公开,**外界** 以及 **子类** 都不能直接访问
> * **私有属性、方法** 通常用于做一些内部的事情

**示例**

![013_父类的私有属性和私有方法-w220](media/15006307099794/013_%E7%88%B6%E7%B1%BB%E7%9A%84%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7%E5%92%8C%E7%A7%81%E6%9C%89%E6%96%B9%E6%B3%95.png)

* `B` 的对象不能直接访问 `__num2` 属性
* `B` 的对象不能在 `demo` 方法内访问 `__num2` 属性
* `B` 的对象可以在 `demo` 方法内,调用父类的 `test` 方法
* 父类的 `test` 方法内部,能够访问 `__num2` 属性和 `__test` 方法

## 02. 多继承

**概念**

* **子类** 可以拥有 **多个父类**,并且具有 **所有父类** 的 **属性** 和 **方法**
* 例如:**孩子** 会继承自己 **父亲** 和 **母亲** 的 **特性**

![014_多继承-w384](media/15006307099794/014_%E5%A4%9A%E7%BB%A7%E6%89%BF.png)

**语法**

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

### 2.1 多继承的使用注意事项

**问题的提出**

* 如果 **不同的父类** 中存在 **同名的方法**,**子类对象** 在调用方法时,会调用 **哪一个父类中**的方法呢?

> 提示:**开发时,应该尽量避免这种容易产生混淆的情况!** —— 如果 **父类之间** 存在 **同名的属性或者方法**,应该 **尽量避免** 使用多继承

![015_多继承II-w384](media/15006307099794/015_%E5%A4%9A%E7%BB%A7%E6%89%BFII.png)

#### Python 中的 MRO —— 方法搜索顺序(知道)

* `Python` 中针对 **类** 提供了一个 **内置属性** `__mro__` 可以查看 **方法** 搜索顺序
* MRO 是 `method resolution order`,主要用于 **在多继承时判断 方法、属性 的调用 路径**

```python
print(C.__mro__)
```

**输出结果**

```
(, , , )
```

* 在搜索方法时,是按照 `__mro__` 的输出结果 **从左至右** 的顺序查找的
* 如果在当前类中 **找到方法,就直接执行,不再搜索**
* 如果 **没有找到,就查找下一个类** 中是否有对应的方法,**如果找到,就直接执行,不再搜索**
* 如果找到最后一个类,还没有找到方法,程序报错

### 2.2 新式类与旧式(经典)类

> `object` 是 `Python` 为所有对象提供的 **基类**,提供有一些内置的属性和方法,可以使用 `dir` 函数查看

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

* 在 `Python 3.x` 中定义类时,如果没有指定父类,会 **默认使用** `object` 作为该类的 **基类** —— `Python 3.x` 中定义的类都是 **新式类**
* 在 `Python 2.x` 中定义类时,如果没有指定父类,则不会以 `object` 作为 **基类**

> **新式类** 和 **经典类** 在多继承时 —— **会影响到方法的搜索顺序**

为了保证编写的代码能够同时在 `Python 2.x` 和 `Python 3.x` 运行!
今后在定义类时,**如果没有父类,建议统一继承自 `object`**

```python
class 类名(object):
    pass
```

====

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章