深度学习——第3章 Python程序设计语言(3.5 Python类和对象)

3.5 Python类和对象

目录

[1. 面向对象的基本概念](#1. 面向对象的基本概念)

[2. 类和对象的关系](#2. 类和对象的关系)

[3. 类的声明](#3. 类的声明)

[4. 对象的创建和使用](#4. 对象的创建和使用)

[5. 类对象属性](#5. 类对象属性)

[6. 类对象方法](#6. 类对象方法)

[7. 面向对象的三个基本特征](#7. 面向对象的三个基本特征)

[8. 综合案例:汉诺塔图形化移动](#8. 综合案例:汉诺塔图形化移动)

1.1 面向对象的基本概念

1.1.1 对象(object)概念引入

在现实生活中,随处可见的事物就是对象,对象是事物存在的实体。

小狗就是真实世界的一个对象,应该如何描述这个对象呢?可以从以下两个方面来讨论:

(1)静态特征:比如四条腿、一条尾巴、两只耳朵、黑色...

(2)动态特征:比如它跑得很快、喜欢睡懒觉、吃东西时会流口水、见认识的人会摇尾巴,见了陌生人会"汪汪"大叫...

从静态特征和动态特征两方面就可以简单地将小狗这个对象来描述清楚。如果把人作为一个对象,你会从哪些方面来描述呢?

长相(高矮胖瘦)、爱好(运动、书法)

1.1.2 对象的定义

对象(object),从概念层面讲,就是某种事物的抽象。抽象原则包括两个方面:数据抽象,定义对象的属性;过程抽象,定义对象的操作。

面向对象的程序设计强调把数据(属性)和操作(服务)结合为一个不可分的系统单位(即对象),对象的外部只需要知道它做什么,而不需要知道它如何做。

从规格层面讲,对象是一系列可以被其他对象使用的公共接口(对象交互)。从语言实现层面来看,对象封装了数据和代码(数据和程序)。

Python中的对象也是如此,对象的静态特征称为"属性",对象的动态特征称为方法

概括起来讲:对象 = 属性 + 方法

特性的描述称为属性,在代码层面来看其实就是变量。

方法实际就是函数,通过调用这些函数来完成操作。

1.1.3 面向对象编程

面向过程(procedure-oriented):通过把解决问题的步骤写出来,让程序一步一步去执行,直到解决问题,是一种以事件为中心的编程。

面向对象(Object-Oriented):一种以事物为中心的编程思想。

面向对象编程是较"高层"的编程架构。

1.2 类和对象的关系

类是抽象的、宏观的,是一种描述。而对象是具体的、个性的。

类是数据结构,类定义数据类型的数据(属性)和行为(方法)。

对象是类的具体实体,也可称为类的实例(Instance)。

类与对象的关系类似于车型设计和具体车辆之间的关系。车型设计(类)描述了该车型所应该具备的属性和性能,但车型设计并不是具体的车,不能发动和驾驶。具体的一辆车(车类的实例),具备该车型设计所描述的属性和功能,可以发动和驾驶。

1.3 类的声明

类的声明(定义):Python中使用class关键字来定义类,其中属性也称成员变量,方法也称成员函数。格式如下:

class 类名:
	类体(属性、方法)

类名为有效的标识符,命名规则一般为多个单词组成的名称,每个单词除第一个字母大写外,其余的字母均小写(Python中的类名约定以大写字母开头);类体由缩进的语句块构成。

定义在类体内的元素都是类的成员。类的主要成员包括两种类型,即描述状态的数据成员(属性)和描述操作的函数成员(方法)。

python 复制代码
# Dog类声明示例
class Dog:
    leg=4; tail=1; ear=2 ; weight=5  #属性
    def run(self):             #方法1
        print("小狗正在飞快地跑")    
    def sleep(self):           #方法2
        print("小狗正在睡觉")
    def eat(self):             #方法3
        print("食物真好吃")
python 复制代码
d=Dog()
print(d.leg)
d.run()
d.sleep()
d.eat()

输出结果:

python 复制代码
4
小狗正在飞快地跑
小狗正在睡觉
食物真好吃

1.4 对象的创建和使用

类是抽象的,要使用类定义的功能,就必须实例化类,即创建类的对象。创建对象后,可以使用". "运算符来调用其成员。实例出来的对象具有与类相同的属性和方法。

注意:创建类的对象、创建类的实例、实例化类等说法是等价的,都说明以类为模板生成了一个新的对象。

对象的创建格式:anObject = 类名(参数列表)

对象的调用格式:anObject.对象函数 或 anObject.对象属性

python 复制代码
# 类对象的创建和调用示例
mydog=Dog()                           #创建名为mydog的对象
w1=mydog.weight                        #访问mydog对象中weight属性
print("我的小狗重:{}kg".format(w1))           #输出:我的小狗重:5kg
mydog.run()                           #输出:我正在飞快地跑
mydog.sleep()                          #输出:我正在睡觉

输出结果:

python 复制代码
我的小狗重:5kg
小狗正在飞快地跑
小狗正在睡觉

1.5 类对象属性

类的数据成员是在类中定义的成员变量,用来存储描述类的特征的值,称为属性。属性可以被该类中定义的方法访问,也可以通过类或类的实例进行访问。在函数体或代码块中定义的局部变量,则只能在其定义的范围内进行访问。

属性实际上是在类中的变量。Python变量不需要声明,可直接使用。建议在类定义开始位置初始化类属性,或者在构造函数(init)中初始化对象属性。

1.5.1 对象属性

在类中定义的属性,最好是通用的属性。

前面的Dog类中定义的weight=5其实是个不很恰当的示范,它应该定义在对象的方法中,在__init__ 方法中定义才为恰当。

通过self.变量名定义的属性,称为对象属性(实例属性),也称为对象变量。类的每个实例都包含该类的对象变量单独副本,对象变量属于特定的实例。

对象变量在类的内部通过self访问,在外部通过对象实例访问。

:Python中的self等价于C++中的this指针和Java、C#中的this关键字。

1.5.2 类属性

Python允许声明属于类本身的变量,即类属性,也称为类变量、静态属性。类属性属于整个类,不是特定实例的一部分,而是所有实例共享一个副本。

类属性一般在类体中通过如下形式初始化:

类变量名 = 初始值

然后,在其类定义的方法中或外部代码中,通过类名访问:

类名.类变量名 = 值       #写入
类名.类变量名            #读取
python 复制代码
# 类属性示例(c16_01_Person.py)
class Person:
    count = 0
    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.count += 1
    def __del__(self):
        Person.count -= 1
    def say_hi(self):
        print("您好!我叫:{}".format(self.name))
    def getcount():
        print("计数:{}".format(Person.count))
print("现有人数:{}".format(Person.count))
p31 = Person("张三", 25)
p31.say_hi()
Person.getcount()
p32 = Person("李四", 28)
p32.say_hi()
Person.getcount()
del p31
Person.getcount()
del p32
Person.getcount()
python 复制代码
现有人数:0
您好!我叫:张三
计数:1
您好!我叫:李四
计数:2
计数:1
计数:0

1.5.3 对象属性和类属性的区别

类属性通过"类名.属性名"的方式访问;对象属性通过"对象名.属性名"的方式访问。如果类属性通过"对象名.属性名"来访问,则转化为该对象实例的对象属性。

python 复制代码
# 类属性和对象属性示例
class A:
    name='aaa'
    
a = A()
b = A()

A.name = 'AAA'
print(a.name , b.name)              #输出:('AAA', 'AAA')

b.name = 'BBB'
print(a.name)                     #输出:'AAA'
print(b.name)                     #输出:'BBB'
print(id(a.name),id(b.name))
print(id(A.name))

输出结果:

python 复制代码
AAA AAA
AAA
BBB
1484146593648 1484146337456
1484146593648

1.5.4 私有属性和公有属性

Python类的成员没有访问控制限制,这与其他面向对象的语言不同。

公有属性:前面类中定义的属性都是公有属性,可以通过"对象名.成员"的方式来进行访问。

私有属性:Python定义私有类型的方式很简单,约定两个下划线"__"开头、但是不以两个下划线结束的属性是私有的(Private),其他即为公有的(Public)。不能直接访问私有属性,但可以在类方法中访问。

python 复制代码
# 类属性和对象属性示例
class Dog:
    name="旺财"
    __age=1
    
dog = Dog()
dog.name     #输出: '旺财'
# dog.__age    #输出: AttributeError: 'Dog' object has no attribute '__age'

如何访问私有属性?

类的外部不能直接访问私有属性,但我们可以利用类内部的方法,通过"self.私有成员名"的方式进行访问。

私有属性是为数据的封装和保密设计的,一般只能在类的内部访问。

python 复制代码
# 类属性和对象属性示例
class Dog:
    __name="旺财"              #私有属性
    def getName(self):           #在方法中获取私有属性值
        return self.__name
    
mydog=Dog()
# print(mydog.__name)
print(mydog.getName())             #输出:旺财

1.6 类对象方法

1.6.1 方法的声明和调用

方法是与类相关的函数,它的定义与普通的函数一致,区分只在于类的方法定义在类体中,且其第一个形式参数必须为对象本身,通常为self。

方法的声明格式:

def 方法名(self,[形参列表]):
	函数体  

方法的调用格式:

对象.方法名([实参列表])

注意:虽然类方法的第一个参数为self,但调用时,用户不需要也不能给该参数传值,Python会自动把对象实例传递给该参数。

1.6.2 self变量

类方法中都至少有一个名为self的参数,并且是第一个参数,用于表示对象本身,在调用方法时不需要为这个参数赋值。由同一个类可以生成无数个对象,当一个对象的方法被调用的时候,对象会将自身作为一个参数传给该方法,Python知道需要操作哪个对象和方法。

例如:假设已声明一个类MyClass,其中包括类方法my_func(self,p1,p2)。

在以下代码中:

obj1 = MyClass()          #创建MyClass实例obj1
obj1.my_func(p1,p2)       #调用对象obj1的方法

调用对象obj1的方法obj1.my_func(p1,p2),Python自动转化为:obj1.my_func(obj1,p1,p2),即自动把对象实例obj1传值给self参数。

备注:Python中的self等价于C++中的this指针和Java、C#中的this关键字。虽然没有限制第一个参数名必须是self,但建议大家遵循惯例,这样便于阅读和理解,且集成开发环境(IDE)也会提供相应的支持。

示例:定义Dog类中的两个方法:setowner()和getowner(),分别为设置主人的姓名和获取主人的姓名。

python 复制代码
# self作用示例
class Dog:
    def setowner(self, name):                 # 方法1:设置对象的主人姓名
        self.owner = name
    def getowner(self):                     # 方法2:获取对象的主人姓名
        print("我的主人是:{}".format(self.owner))
    def run(self):                        # 方法3
        pass
a = Dog()                       # 创建小狗对象a
a.setowner("张三")                 # 张三认领小狗a
b = Dog()                       # 创建小狗对象b
b.setowner("李四")                 # 李四认领小狗b
a.getowner()                     # 输出:我的主人是:张三
b.getowner()                     # 输出:我的主人是:李四

1.6.3 __init__方法(构造函数)

Python类体中,可以定义特殊的方法:__init__方法。

__init__是特殊的方法,在个方法名称的开头和末尾各有两个下划线,每当我们创建对象时都会自动调用。

__init__方法即构造函数(构造方法),用于执行类实例化的初始化工作。

__init__方法在创建完对象后调用,初始化当前对象,无返回值。

init,单词initialize的简写,初始化之意。

python 复制代码
# __init__的使用示例
class Dog:
    leg=4; tail=1; ear=2 
    def __init__(self,a,b):
        self.name=a
        self.owner=b
        
dog1=Dog("旺财1号","Adam")
dog2=Dog("旺财2号","Grit")

print(dog1.owner,"的小狗叫",dog1.name)    #输出: Adam 的小狗叫 旺财1号
print(dog2.owner,"的小狗叫",dog2.name)    #输出: Grit 的小狗叫 旺财2号

dog1.name = "改名1"
dog2.owner = "Lucy"

print(dog1.owner,"的小狗叫",dog1.name)    #输出: Adam 的小狗叫 改名1
print(dog2.owner,"的小狗叫",dog2.name)    #输出: Lucy 的小狗叫 旺财2号

1.6.5 __del__方法(析构函数)

Python类体中,可以定义一个特殊的方法:__del__方法。

__del__方法即析构函数(析构方法),用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(如打开的文件,网络连接等)。

默认情况下,当对象不再被使用时,__del__方法运行,由于Python解释器实现自动垃圾回收,即无法保证这个方法究竟在什么时间运行。

通过del语句,可以强制销毁一个对象实例,从而保证调用对象实例的__del__方法。

1.7 面向对象的三个基本特征

面向对象的程序设计具有三个基本特征:封装继承多态,可以大大增加程序的可靠性、代码的可重用性和程序的可维护性,从而提高程序开发效率。

1.7.1 封装

封装(Encapsulation)是面向对象的主要特征。所谓封装,也就是把客观事物抽象并封装成对象,即将数据成员、属性、方法和事件等集合在一个整体内。通过访问控制,还可以隐藏内部成员,只允许可信的对象访问或操作自己的部分数据或方法。

封装保证了对象的独立性,可以防止外部程序破坏对象的内容数据,同时便于程序的维护和修改。

生活中处处都是封装的概念,比如家里的电视机,从开机,浏览节目,换台到关机,我们不需要知道电视机里面的具体细节,只需要在用的时候按下遥控器就可以完成操作;又比如,在使用移动支付的时候,只需要把付款二维码提供给收款方扫一下就可以完成支付,不需要知道后台是怎样处理数据的,这都体现了一种封装的概念。

把各类数据放进列表,是一种封装,是数据层面的封装;把常用的代码打包为一个函数,也是一种封装,是语句层面的封装;面向对象的封装,更先进一步,即有数据又有函数。

在面向对象的编程语言中"封装"就是将抽象得到的属性和行为相结合,形成一个有机的整体,即类。

一个房间有名称name、房间主人的名字owner、房间长length、宽width、高height等属性,我们构建了 Room类并实例化了一个对象,现在若需要计算房间面积,可定义函数 area()。

python 复制代码
# 封装示例------Room类

class Room:                              # 定义一个房间的类
    def __init__(self, n, o, l, w, h):
        self.name = n
        self.owner = o
        self.length = l                  # 房间的长
        self.width = w                   # 房间的宽
        self.height = h                  # 房间的高
        
r1 = Room("客厅", "Adam", 20, 30, 9)            # 类的实例化

def area(room):
    print("{0}的{1}面积是{2}".format(room.owner, room.name, room.length * room.width))
    
area(r1)                               # 输出:Adam的客厅面积是600

封装可以简化编程,使用者不必了解具体的实现细节。可以直接在Room类的内部定义访问数据的函数area ()。

python 复制代码
# 封装示例------Room类
class Room:                              # 定义一个房间的类
    def __init__(self, n, o, l, w, h):
        self.name = n
        self.owner = o
        self.length = l                  # 房间的长
        self.width = w                   # 房间的宽
        self.height = h                  # 房间的高
    def area(self):                      # 计算房间面积
        print("{0}的{1}面积{2}".format(self.owner, self.name, self.length * self.width))

r1 = Room("客厅", "Adam", 20, 30, 9)            # 实例化一个房间
r1.area()                              # 输出:Adam的客厅面积是600

封装带来的好处是增强安全性。

封装也提供了良好的可扩展性。

思考:如果在上述例子中增加求体积的功能,仍然在area方法中实现,使功能增加,该如何改动代码?

python 复制代码
# 封装示例------Room类
class Room:                              # 定义一个房间的类
    def __init__(self, n, o, l, w, h):
        self.name = n;self.owner = o
        self.length = l                  # 房间的长
        self.width = w                   # 房间的宽
        self.height = h                  # 房间的高
    def area(self):                      # 计算房间面积
        print("{0}的{1}面积为{2},".format(self.owner, self.name, self.length * self.width), end="")
        print("体积是{0}。".format(self.length * self.width * self.height))
        
r1 = Room("客厅", "Adam", 20, 30, 9)            # 实例化一个房间
r1.area()                              # 输出:Adam的客厅面积600体积是5400

1.7.2 继承

(1)继承的概念

继承(Inheritance)是面向对象的程序设计中代码重用的主要方法。继承允许使用现有类的功能,并在无需重新改写原来的类的情况下,对这个功能进行扩展。继承可以避免代码复制和相关代码维护等问题。

继承的过程,就是从一般到特殊的过程。被继承的类称为"基类 (Base Class)"、"父类 "或"超类 (Super Class)",通过继承创建的新类称为"子类 (Subclass)"或"派生类(Derived Class)"。

知识要点

  • 被继承的类称为父类,继承者称为子类。

  • 子类是父类的特殊化,子类继承了父类的特性,同时可以对继承到的特性进行更改,也可以拥有父类没有的特性。

(2)继承的方法

Python支持多重继承,一个子类可以继承于多个父类。子类的声明格式如下:

class 子类名(父类1,[父类2,...]):
	类体

子类名后为所有父类的名称元组。

如果在类定义中没有指定父类,则默认其父类为object。object是所有对象的根父类,定义了公用方法的默认实现,如__new__()。

例如:

class Foo: pass

等同于:

class Foo(object):pass

声明子类时,必须在其构造函数中调用父类的构造函数。

调用格式如下:

父类名.__init__(self,参数列表)
python 复制代码
#父类和子类示例

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def say_hi(self):
        print('您好!我是{0},今年{1}岁。'.format(self.name,self.age))
        
class Student(Person):
    def __init__(self,name,age,stu_id):
        Person.__init__(self,name,age)
        self.stu_id = stu_id
    def say_hi(self):
        Person.say_hi(self)
        print('我是学生,我的学号是:{}。'.format(self.stu_id))

#父类和子类示例续
p1 = Person('张三',33)
p1.say_hi()
s1 = Student('李四',20,'P171823001')
s1.say_hi()

输出结果:

python 复制代码
您好!我是张三,今年33岁。
您好!我是李四,今年20岁。
我是学生,我的学号是:P171823001。
(3)类成员的继承和重写

通过继承,子类继承父类中除构造方法之外的所有成员。如果在子类中重新定义从父类继承的方法,则子类中定义的方法覆盖从父类中继承的方法。:若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,这称之为方法重写。

程序案例:定义父类Dimension,包含2个数据成员x和y;定义继承于Dimension的两个子类Circle和Rectangle(Circle只有一个数据成员r),重写area方法输出信息。定义两个实例输出相关信息。

python 复制代码
# 类成员的继承和重写示例
import math
class Dimension:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def area(self):
        pass

    def show_info(self):
        print("Dimension类的show_info方法。")
        
class Circle(Dimension):
    def __init__(self, r):
        Dimension.__init__(self, r, 0)
    def area(self):
        return round(math.pi*self.x**2,2)

class Rectangle(Dimension):
    def __init__(self, w, h):
        Dimension.__init__(self, w, h)
    def area(self):
        return self.x * self.y
    
d1 = Circle(2.0)
d2 = Rectangle(2.0, 4.0)
print(d1.area(), "\n", d2.area(),sep="")
d1.show_info()
d2.show_info()

输出结果:

python 复制代码
12.57
8.0
Dimension类的show_info方法。
Dimension类的show_info方法。
(4)输出继承关系

多个类的继承可以形成层次关系,通过类的方法mro()或类的属性__mro__可以输出其继承的关系。例如:

python 复制代码
# 输出继承关系示例

class A: pass
class B: pass
class C(A) : pass
class D(B) : pass
class E(A,B): pass
print(C.mro())
print(E.mro())

输出结果:

python 复制代码
[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.E'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
(5)super()函数

super函数能够帮助我们自动找到父类的方法,而且还不用传入self参数,可以省事一些。

super函数的"超级"之处在于不需要明确给出任何父类的名字,它会自动帮我们找出所有父类以及对应的方法。由于不用给出父类名称,这意味着如果需要改变类的继承关系,只要改变class语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。

python 复制代码
# 类成员的继承和重写示例
import math
class Dimension:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def area(self):
        pass

    def show_info(self):
        print("Dimension类的show_info方法。")
        
class Circle(Dimension):
    def __init__(self, r):
        super().__init__(r, 0)         #此处有改变
    def area(self):
        return round(math.pi*self.x**2,2)

class Rectangle(Dimension):
    def __init__(self, w, h):
        super().__init__(w, h)         #此处有改变
    def area(self):
        return self.x * self.y
    
d1 = Circle(2.0)
d2 = Rectangle(2.0, 4.0)
print(d1.area(), "\n", d2.area(),sep="")
d1.show_info()
d2.show_info()

输出结果:

python 复制代码
12.57
8.0
Dimension类的show_info方法。
Dimension类的show_info方法。

1.7.3 多态

多态是多种表现形态的意思,它是一种机制、一种能力,而非某个关键字。在面向对象的编程中,多态在类的继承和类的方法调用中得以体现。多态在不清楚对象的具体类型时,根据引用对象的不同而表现出不同的行为方式。当不知道对象到底是什么类型,但又要让对象"做点儿什么"的时候,都会用到多态。

对象可以表示多个类型的能力称为多态(Polymorphism)。多态性允许每个对象以自己的方式去响应共同的消息,从而允许用户以更明确的方式建立通用软件,提高软件开发的可维护性。

Python是一种动态语言,变量或参数无法也无需确定其类型。程序运行过程中,根据实际的对象类型确定变量的类型。

严格意义上讲,Python不支持多态性,即动态确定变量指向的对象类型。

(1)对象的多态表现

声明Dog类和Cat类,同时包括方法move,分别创建dog对象和cat对象,调用move方法,然后根据其类型的不同,它会表现出不同的行为。

python 复制代码
# 对象的多态表现示例
from random import choice
class Dog:
    def move(self):
        print("飞快地跑!")
class Cat:
    def move(self):
        print("慢悠悠地走。")
        
dog = Dog()
cat = Cat()

obj = choice([dog, cat])  # choice函数实现从列表中随机选择一个元素

print(type(obj))  # type函数可以查看对象类型
obj.move()      # 若obj为Cat,输出慢悠悠地走,若obj为Dog,输出飞快地跑!

输出结果:

python 复制代码
<class '__main__.Dog'>
飞快地跑!
(2)函数和运算符的多态表现
python 复制代码
# 函数和运算符的多态表现示例
def sum(a, b):
    return a + b
a = 1
b = 2
c = "深度"
d = "学习"
print(sum(a, b))
print(sum(c, d))

输出结果:

python 复制代码
3
深度学习
(3) Python应用多态注意事项

Python和其他静态形态检查类的语言(如C++等)不同,在参数传递时不管参数是什么类型,都会按照原有的代码顺序执行,这就很可能会出现因传递的参数类型错误而导致程序崩溃的现象。

python 复制代码
# 示例
def is_odd_number(a):
    return not a%2==0

a = 3
print(is_odd_number(a)) # 输出:True

b = 2.1
print(is_odd_number(b)) # 输出:True(没报错,但结果是错的)

c = [3]
# print(is_odd_number(c)) 
# 输出:TypeError: unsupported operand type(s) for %: 'list' and 'int'

思考:怎样避免这种因传递的参数类型错误而导致程序崩溃的现象?

安全起见,需要自己编写代码来检验参数类型的正确性。

python 复制代码
# 示例
def is_odd_number(a):
    if isinstance(a,int):
        return not a%2==0
    else:
        return -1   #类型不合法时返回-1
    
a = 2
print(is_odd_number(a))       # 输出:False
b = 2.1
print(is_odd_number(b))       # 输出:-1
c = [3]
print(is_odd_number(c))       # 输出:-1

1.8 综合案例:汉诺塔图形化移动

案例名称:汉诺塔的图形化移动

问题描述:图形化实现将汉诺塔上的N个盘子按规则移动到右侧。

解题思路:

  1. 声明"塔柱"类,数据存储塔柱上的盘子(列表),方法包括向"塔柱"添加盘子(列表增加)、移去盘子(列表删除)、获取塔柱的高度(列表长度)以及最高处存放的盘子编号(列表最后一个值)等。

  2. 导入turtle库,创建并画出三个塔柱。同时创建塔柱类,并初始化第一个塔柱数据。

  3. 创建N个盘子(列表)。画出N个盘子,外形为方形,将盘子编号和外形尺寸对应起来。

  4. 移动汉诺塔,将数据层面的移动和图形化移动结合起来。注意,要先完成图形化移动,再实现数据层面移动。

递归移动方法:将n个盘子中的第n个看作为一个大盘,剩余n-1个盘子看作为一个小盘。先借助最右侧柱移动小盘到中间柱,再移动大盘到右侧柱,最后借助最左侧柱移动中间柱上的小盘到右侧柱。

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。传说大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

代码示例如下:

python 复制代码
import turtle as t
from random import random

N = 3  # 要移动的盘数


class Pole:  # 定义杆类
    def __init__(self):  # 初始化列表,存储本杆当前的盘子
        self.plates = []

    def platePush(self, i):  # 列表中添加元素(入栈)
        self.plates.append(i)

    def platePop(self):  # 列表中减少一个元素(出栈)
        return self.plates.pop()

    def poleHeight(self):  # 返回当前杆的高度,即元素个数
        return len(self.plates)


def draw_create_poles(n):  # 画出三个杆,定义并返回N个盘子的列表
    p = [t.Turtle() for i in range(3)]
    t.setup(1200, 500)
    t.hideturtle()
    for i in range(3):
        p[i].penup()
        p[i].hideturtle()
        p[i].goto(400 * (i - 1), 100)
        p[i].pendown()
        p[i].pensize(10)
        p[i].color(random(), random(), random())
        p[i].goto(400 * (i - 1), -100)
        p[i].goto(400 * (i - 1) - 20, -100)
        p[i].goto(400 * (i - 1) + 20, -100)
    pl = [Pole() for i in range(3)]  # 定义三个杆列表
    for i in range(N):  # 向第一个杆中放入N个盘子
        pl[0].platePush(i)
    return pl


def draw_create_plates(n):  # 创建、画出并返回N个盘子的列表
    p = [t.Turtle() for i in range(n)]
    for i in range(n):
        p[i].shape("square")
        p[i].color(random(), random(), random())
        p[i].penup()
        p[i].shapesize(1, 8 - i)
        p[i].goto(-400, -90 + 20 * i)
    return p


def move_plate(pl, pa, a, b):
    i = pl[a].plates[len(pl[a].plates) - 1]
    pa[i].goto(400 * (a - 1), 120)
    pa[i].goto(400 * (b - 1), 120)
    j = pl[b].poleHeight()
    pa[i].goto(400 * (b - 1), -90 + 20 * j)


def hanoi(poleList, plates, n, a, b, c):

    if n >= 1:
        hanoi(poleList, plates, n - 1, a, c, b)
        print("从{}柱移动到{}柱。".format(a, c))
        move_plate(poleList, plates, a, c)
        poleList[c].platePush(poleList[a].platePop())
        hanoi(poleList, plates, n - 1, b, a, c)


def main():
    pl = draw_create_poles(N)
    pa = draw_create_plates(N)
    hanoi(pl, pa, N, 0, 1, 2)
    t.mainloop()    

if __name__ == "__main__":
    main()

输出文字示例如下,动画效果自行尝试:

python 复制代码
从0柱移动到2柱。
从0柱移动到1柱。
从2柱移动到1柱。
从0柱移动到2柱。
从1柱移动到0柱。
从1柱移动到2柱。
从0柱移动到2柱。
相关推荐
Auc24几秒前
使用scrapy框架爬取微博热搜榜
开发语言·python
QQ_7781329748 分钟前
基于深度学习的图像超分辨率重建
人工智能·机器学习·超分辨率重建
梦想画家17 分钟前
Python Polars快速入门指南:LazyFrames
python·数据分析·polars
清 晨20 分钟前
Web3 生态全景:创新与发展之路
人工智能·web3·去中心化·智能合约
程序猿000001号30 分钟前
使用Python的Seaborn库进行数据可视化
开发语言·python·信息可视化
API快乐传递者39 分钟前
Python爬虫获取淘宝详情接口详细解析
开发语言·爬虫·python
公众号Codewar原创作者41 分钟前
R数据分析:工具变量回归的做法和解释,实例解析
开发语言·人工智能·python
FL16238631291 小时前
python版本的Selenium的下载及chrome环境搭建和简单使用
chrome·python·selenium
巫师不要去魔法部乱说1 小时前
PyCharm专项训练5 最短路径算法
python·算法·pycharm
Chloe.Zz1 小时前
Python基础知识回顾
python