37、面向对象编程中的多态与递归

面向对象编程中的多态与递归

1. 多态的概念

多态是面向对象编程中一个强大的特性,它允许子类拥有与父类同名的方法。多态的核心在于,程序能够根据调用方法的对象类型,调用正确的方法版本。多态行为主要包含以下两个关键要素:
- 方法重写 :可以在父类中定义一个方法,然后在子类中定义一个同名的方法。当子类方法与父类方法同名时,通常称子类方法重写了父类方法。例如,每个子类的 __init__ 方法通常会重写父类的 __init__ 方法。
- 正确调用重写方法 :根据调用方法的对象类型,调用正确版本的重写方法。如果使用子类对象调用重写方法,则执行子类的方法版本;如果使用父类对象调用重写方法,则执行父类的方法版本。

1.1 示例代码

下面通过一个简单的例子来演示多态。首先定义一个 Mammal 类:

# The Mammal class represents a generic mammal.
class Mammal:
    # The __init__ method accepts an argument for
    # the mammal's species.
    def __init__(self, species):
        self._species = species

    # The show_species method displays a message
    # indicating the mammal's species.
    def show_species(self):
        print('I am a', self._species)

    # The make_sound method is the mammal's
    # way of making a generic sound.
    def make_sound(self):
        print('Grrrrr')

创建 Mammal 类的实例并调用其方法的示例代码如下:

import animals
mammal = animals.Mammal('regular mammal')
mammal.show_species()
mammal.make_sound()

上述代码的输出结果为:

I am a regular mammal
Grrrrr

1.2 子类示例

接下来定义 Mammal 类的子类 Dog Cat

# The Dog class is a subclass of the Mammal class.
class Dog(Mammal):
    # The __init__ method calls the superclass's
    # __init__ method passing 'Dog' as the species.
    def __init__(self):
        Mammal.__init__(self, 'Dog')

    # The make_sound method overrides the superclass's
    # make_sound method.
    def make_sound(self):
        print('Woof! Woof!')

# The Cat class is a subclass of the Mammal class.
class Cat(Mammal):
    # The __init__ method calls the superclass's
    # __init__ method passing 'Cat' as the species.
    def __init__(self):
        Mammal.__init__(self, 'Cat')

    # The make_sound method overrides the superclass's
    # make_sound method.
    def make_sound(self):
        print('Meow')

创建 Dog Cat 类的实例并调用其方法的示例代码如下:

import animals
dog = animals.Dog()
dog.show_species()
dog.make_sound()

cat = animals.Cat()
cat.show_species()
cat.make_sound()

上述代码的输出结果为:

I am a Dog
Woof! Woof!
I am a Cat
Meow

1.3 isinstance 函数的使用

多态在程序设计中提供了很大的灵活性。例如,定义一个函数 show_mammal_info

def show_mammal_info(creature):
    creature.show_species()
    creature.make_sound()

只要传递给该函数的对象具有 show_species make_sound 方法,函数就会调用这些方法。但如果传递的对象不是 Mammal 类或其子类的实例,就会引发 AttributeError 异常。可以使用内置函数 isinstance 来避免这种异常的发生。 isinstance 函数的一般调用格式为:

isinstance(object, ClassName)

以下是使用 isinstance 函数改进后的 show_mammal_info 函数示例:

# This program demonstrates polymorphism.
import animals

def main():
    # Create a Mammal object, a Dog object, and
    # a Cat object.
    mammal = animals.Mammal('regular animal')
    dog = animals.Dog()
    cat = animals.Cat()

    # Display information about each one.
    print('Here are some animals and')
    print('the sounds they make.')
    print('--------------------------')
    show_mammal_info(mammal)
    print()
    show_mammal_info(dog)
    print()
    show_mammal_info(cat)
    print()
    show_mammal_info('I am a string')

# The show_mammal_info function accepts an object
# as an argument and calls its show_species
# and make_sound methods.
def show_mammal_info(creature):
    if isinstance(creature, animals.Mammal):
        creature.show_species()
        creature.make_sound()
    else:
        print('That is not a Mammal!')

# Call the main function.
main()

上述代码的输出结果为:

Here are some animals and
the sounds they make.
--------------------------
I am a regular animal
Grrrrr
I am a Dog
Woof! Woof!
I am a Cat
Meow
That is not a Mammal!

1.4 多态的流程图

graph TD;
    A[开始] --> B[创建对象];
    B --> C{对象是否为 Mammal 或子类};
    C -- 是 --> D[调用 show_species 和 make_sound 方法];
    C -- 否 --> E[输出错误信息];
    D --> F[结束];
    E --> F;

1.5 多态的检查点与练习

检查点

给定以下类定义:

class Vegetable:
    def __init__(self, vegtype):
        self.__vegtype = vegtype

    def message(self):
        print("I'm a vegetable.")

class Potato(Vegetable):
    def __init__(self):
        Vegetable.__init__(self, 'potato')

    def message(self):
        print("I'm a potato.")

执行以下语句:

v = Vegetable('veggie')
p = Potato()
v.message()
p.message()

输出结果为:

I'm a vegetable.
I'm a potato.
复习问题
  • 选择题
    1. 在继承关系中, _ ___ 是通用类。
      A. 子类
      B. 父类
      C. 从属类
      D. 子类
    2. 在继承关系中, _ ___ 是专用类。
      A. 父类
      B. 主类
      C. 子类
      D. 父类
    3. 假设一个程序使用两个类: Airplane JumboJet 。哪个更有可能是子类?
      A. Airplane
      B. JumboJet
      C. 两者都是
      D. 两者都不是
    4. 面向对象编程的这个特性允许在使用子类实例调用重写方法时,调用正确的方法版本。
      A. 多态
      B. 继承
      C. 泛化
      D. 特化
    5. 可以使用这个来确定一个对象是否是某个类的实例。
      A. in 运算符
      B. is_object_of 函数
      C. isinstance 函数
      D. 程序崩溃时显示的错误信息
  • 判断题
    1. 多态允许在子类中编写与父类同名的方法。
    2. 不能从子类的 __init__ 方法中调用父类的 __init__ 方法。
    3. 子类可以有与父类同名的方法。
    4. 只有 __init__ 方法可以被重写。
    5. 不能使用 isinstance 函数来确定一个对象是否是某个类的子类的实例。
  • 简答题
    1. 子类从父类继承了什么?
    2. 看以下类定义。父类的名称是什么?子类的名称是什么?
class Hammer(Tool):
    pass
3. 什么是重写方法?
  • 算法工作台
    1. 编写 Trout 类定义的第一行。该类应继承 Fish 类。
    2. 给定以下类定义:
class Art:
    def __init__(self, art_type):
        self.__art_type = art_type

    def message(self):
        print("I'm a piece of art.")

class Painting(Art):
    def __init__(self):
        Art.__init__(self, 'painting')

    def message(self):
        print("I'm a painting.")

执行以下语句:

a = Art('sculpture')
p = Painting()
a.message()
p.message()

输出结果是什么?
3. 看以下类定义:

class Bird:
    def __init__(self, bird_type):
        self._bird_type = bird_type

编写一个名为 Duck 的类,它是 Bird 类的子类。 Duck 类的 __init__ 方法应调用 Bird 类的 __init__ 方法,并传递 'duck' 作为参数。

编程练习
  1. 员工和生产工人类 :编写一个 Employee 类,包含员工姓名和员工编号的数据属性。然后编写一个 ProductionWorker 类,它是 Employee 类的子类,包含班次编号(整数,如 1、2 或 3)和每小时工资率的数据属性。工作日分为白班和夜班,班次属性存储员工工作的班次。白班为 1 班,夜班为 2 班。为每个类编写适当的访问器和修改器方法。编写一个程序,创建 ProductionWorker 类的对象,并提示用户输入对象的每个数据属性。将数据存储在对象中,然后使用对象的访问器方法检索并显示在屏幕上。
  2. 班次主管类 :在一个特定的工厂中,班次主管是一名领取薪水的员工,负责监督一个班次。除了薪水外,当他或她的班次达到生产目标时,班次主管还会获得年度奖金。编写一个 ShiftSupervisor 类,它是 Employee 类的子类。 ShiftSupervisor 类应包含年度薪水和年度生产奖金的数据属性。编写一个程序,使用 ShiftSupervisor 对象来演示该类。
  3. 人和客户类 :编写一个名为 Person 的类,包含人的姓名、地址和电话号码的数据属性。然后编写一个名为 Customer 的类,它是 Person 类的子类。 Customer 类应包含客户编号的数据属性和一个布尔型数据属性,指示客户是否希望加入邮件列表。在一个简单的程序中演示 Customer 类的实例。

2. 递归的概念

递归是一种函数调用自身的编程技术。在程序中,通常是一个函数调用另一个函数,但递归函数会调用自身。例如下面的 message 函数:

# This program has a recursive function.
def main():
    message()

def message():
    print('This is a recursive function.')
    message()

# Call the main function.
main()

这个 message 函数会不断地显示字符串 'This is a recursive function' 并调用自身,由于没有停止递归调用的代码,它会像一个无限循环一样不断重复,运行这个程序时,需要按 Ctrl+C 来中断执行。

为了控制递归函数的重复次数,需要添加控制条件。下面是修改后的 message 函数:

# This program has a recursive function.
def main():
    # By passing the argument 5 to the message
    # function we are telling it to display the
    # message five times.
    message(5)

def message(times):
    if times > 0:
        print('This is a recursive function.')
        message(times - 1)

# Call the main function.
main()

2.1 递归函数的执行流程

上述程序的输出结果为:

This is a recursive function.
This is a recursive function.
This is a recursive function.
This is a recursive function.
This is a recursive function.

在这个程序中, message 函数包含一个 if 语句来控制重复。只要 times 参数大于零,就会显示消息并以较小的参数再次调用自身。 main 函数调用 message 函数并传入参数 5,函数会被调用 6 次,第一次从 main 函数调用,其余 5 次是递归调用。递归的深度是 5,当 times 参数为 0 时, if 语句条件为假,函数返回。控制从第六次调用返回到第五次调用中递归调用之后的位置,以此类推,直到所有实例的函数都返回。递归调用的流程如下表所示:
| 调用次数 | times 参数值 |
| ---- | ---- |
| 第一次 | 5 |
| 第二次 | 4 |
| 第三次 | 3 |
| 第四次 | 2 |
| 第五次 | 1 |
| 第六次 | 0 |

2.2 递归函数的流程图

graph TD;
    A[开始] --> B[调用 message 函数,传入参数 5];
    B --> C{times > 0};
    C -- 是 --> D[显示消息];
    D --> E[递归调用 message 函数,传入参数 times - 1];
    E --> C;
    C -- 否 --> F[返回];
    F --> G[结束];

3. 用递归解决问题

3.1 递归解决问题的思路

递归虽然不是解决问题的必需方法,任何可以用递归解决的问题也可以用循环解决,而且递归算法通常比迭代算法效率低,因为函数调用需要计算机执行一些操作,如为参数和局部变量分配内存、存储返回地址等,这些操作会带来额外的开销,而循环则不需要这些开销。但有些重复问题用递归比用循环更容易解决,程序员可能能更快地设计出递归算法。

一般来说,递归函数的工作方式如下:
- 如果问题现在可以不使用递归解决,那么函数就解决它并返回结果。
- 如果问题现在不能解决,那么函数将其简化为一个更小但结构相似的问题,并调用自身来解决这个更小的问题。

为了应用这种方法,首先要确定至少一种可以不使用递归解决问题的情况,这称为基本情况;其次,要确定在其他所有情况下使用递归解决问题的方法,这称为递归情况。在递归情况中,必须将问题简化为原始问题的较小版本,通过每次递归调用减少问题规模,最终会达到基本情况,递归停止。

3.2 递归计算阶乘的示例

在数学中, n! 表示 n 的阶乘,非负整数的阶乘可以定义如下规则:
- 如果 n = 0 ,则 n! = 1
- 如果 n > 0 ,则 n! = 1 * 2 * 3 * ... * n

用递归函数计算阶乘的规则可以表示为:
- 如果 n = 0 ,则 factorial(n) = 1
- 如果 n > 0 ,则 factorial(n) = n * factorial(n - 1)

以下是用 Python 实现的阶乘递归函数:

# This program uses recursion to calculate
# the factorial of a number.
def main():
    # Get a number from the user.
    number = int(input('Enter a nonnegative integer: '))

    # Get the factorial of the number.
    fact = factorial(number)

    # Display the factorial.
    print('The factorial of', number, 'is', fact)

# The factorial function uses recursion to
# calculate the factorial of its argument,
# which is assumed to be nonnegative.
def factorial(num):
    if num == 0:
        return 1
    else:
        return num * factorial(num - 1)

# Call the main function.
main()

3.3 阶乘递归函数的执行过程

假设输入的数字是 4, factorial 函数的执行过程如下表所示:
| 调用次数 | num 参数值 | 返回值 |
| ---- | ---- | ---- |
| 第一次 | 4 | 24 |
| 第二次 | 3 | 6 |
| 第三次 | 2 | 2 |
| 第四次 | 1 | 1 |
| 第五次 | 0 | 1 |

num 不等于 0 时, if 语句的 else 子句执行 return num * factorial(num - 1) ,但在返回值确定之前,需要先确定 factorial(num - 1) 的值。递归调用会一直进行,直到 num 参数为 0,此时函数返回 1,然后逐步计算并返回最终结果。

3.4 阶乘递归函数的流程图

graph TD;
    A[开始] --> B[输入非负整数 n];
    B --> C{num == 0};
    C -- 是 --> D[返回 1];
    C -- 否 --> E[计算 num * factorial(num - 1)];
    E --> F[返回结果];
    D --> F;
    F --> G[结束];

4. 总结

多态和递归是编程中非常重要的概念。多态通过方法重写和根据对象类型调用正确方法版本,为程序设计带来了很大的灵活性,同时可以使用 isinstance 函数避免异常。递归则是一种函数调用自身的技术,适用于解决一些可以分解为相似小问题的重复问题,虽然有额外的开销,但在某些情况下能让程序员更高效地设计算法。在实际编程中,需要根据具体问题选择合适的技术来解决问题。

根据原作 https://pan.quark.cn/s/459657bcfd45 的源码改编 Classic-ML-Methods-Algo 引言 建立这个项目,是为了梳理和总结传统机器学习(Machine Learning)方法(methods)或者算法(algo),和各位同仁相互学习交流. 现在的深度学习本质上来自于传统的神经网络模型,很大程度上是传统机器学习的延续,同时也在不少时候需要结合传统方法来实现. 任何机器学习方法基本的流程结构都是通用的;使用的评价方法也基本通用;使用的一些数学知识也是通用的. 本文在梳理传统机器学习方法算法的同时也会顺便补充这些流程,数学上的知识以供参考. 机器学习 机器学习是人工智能(Artificial Intelligence)的一个分支,也是实现人工智能最重要的手段.区别于传统的基于规则(rule-based)的算法,机器学习可以从数据中获取知识,从而实现规定的任务[Ian Goodfellow and Yoshua Bengio and Aaron Courville的Deep Learning].这些知识可以分为四种: 总结(summarization) 预测(prediction) 估计(estimation) 假想验证(hypothesis testing) 机器学习主要关心的是预测[Varian在Big Data : New Tricks for Econometrics],预测的可以是连续性的输出变量,分类,聚类或者物品之间的有趣关联. 机器学习分类 根据数据配置(setting,是否有标签,可以是连续的也可以是离散的)和任务目标,我们可以将机器学习方法分为四种: 无监督(unsupervised) 训练数据没有给定...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值