深度学习——第3章 Python程序设计语言(3.5 Python类和对象)
3.5 Python类和对象
目录
1. 面向对象的基本概念
2. 类和对象的关系
3. 类的声明
4. 对象的创建和使用
5. 类对象属性
6. 类对象方法
7. 面向对象的三个基本特征
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中的类名约定以大写字母开头);类体由缩进的语句块构成。
定义在类体内的元素都是类的成员。类的主要成员包括两种类型,即描述状态的数据成员(属性)和描述操作的函数成员(方法)。
# Dog类声明示例
class Dog:leg=4; tail=1; ear=2 ; weight=5 #属性def run(self): #方法1print("小狗正在飞快地跑") def sleep(self): #方法2print("小狗正在睡觉")def eat(self): #方法3print("食物真好吃")
d=Dog()
print(d.leg)
d.run()
d.sleep()
d.eat()
输出结果:
4
小狗正在飞快地跑
小狗正在睡觉
食物真好吃
1.4 对象的创建和使用
类是抽象的,要使用类定义的功能,就必须实例化类,即创建类的对象。创建对象后,可以使用“. ”运算符来调用其成员。实例出来的对象具有与类相同的属性和方法。
注意:创建类的对象、创建类的实例、实例化类等说法是等价的,都说明以类为模板生成了一个新的对象。
对象的创建格式:anObject = 类名(参数列表)
对象的调用格式:anObject.对象函数 或 anObject.对象属性
# 类对象的创建和调用示例
mydog=Dog() #创建名为mydog的对象
w1=mydog.weight #访问mydog对象中weight属性
print("我的小狗重:{}kg".format(w1)) #输出:我的小狗重:5kg
mydog.run() #输出:我正在飞快地跑
mydog.sleep() #输出:我正在睡觉
输出结果:
我的小狗重: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允许声明属于类本身的变量,即类属性,也称为类变量、静态属性。类属性属于整个类,不是特定实例的一部分,而是所有实例共享一个副本。
类属性一般在类体中通过如下形式初始化:
类变量名 = 初始值
然后,在其类定义的方法中或外部代码中,通过类名访问:
类名.类变量名 = 值 #写入
类名.类变量名 #读取
# 类属性示例(c16_01_Person.py)
class Person:count = 0def __init__(self, name, age):self.name = nameself.age = agePerson.count += 1def __del__(self):Person.count -= 1def 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()
现有人数:0
您好!我叫:张三
计数:1
您好!我叫:李四
计数:2
计数:1
计数:0
1.5.3 对象属性和类属性的区别
类属性通过“类名.属性名”的方式访问;对象属性通过“对象名.属性名”的方式访问。如果类属性通过“对象名.属性名”来访问,则转化为该对象实例的对象属性。
# 类属性和对象属性示例
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))
输出结果:
AAA AAA
AAA
BBB
1484146593648 1484146337456
1484146593648
1.5.4 私有属性和公有属性
Python类的成员没有访问控制限制,这与其他面向对象的语言不同。
公有属性:前面类中定义的属性都是公有属性,可以通过“对象名.成员”的方式来进行访问。
私有属性:Python定义私有类型的方式很简单,约定两个下划线“__”开头、但是不以两个下划线结束的属性是私有的(Private),其他即为公有的(Public)。不能直接访问私有属性,但可以在类方法中访问。
# 类属性和对象属性示例
class Dog:name="旺财"__age=1dog = Dog()
dog.name #输出: '旺财'
# dog.__age #输出: AttributeError: 'Dog' object has no attribute '__age'
如何访问私有属性?
类的外部不能直接访问私有属性,但我们可以利用类内部的方法,通过“self.私有成员名”的方式进行访问。
私有属性是为数据的封装和保密设计的,一般只能在类的内部访问。
# 类属性和对象属性示例
class Dog:__name="旺财" #私有属性def getName(self): #在方法中获取私有属性值return self.__namemydog=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(),分别为设置主人的姓名和获取主人的姓名。
# self作用示例
class Dog:def setowner(self, name): # 方法1:设置对象的主人姓名self.owner = namedef getowner(self): # 方法2:获取对象的主人姓名print("我的主人是:{}".format(self.owner))def run(self): # 方法3pass
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的简写,初始化之意。
# __init__的使用示例
class Dog:leg=4; tail=1; ear=2 def __init__(self,a,b):self.name=aself.owner=bdog1=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()。
# 封装示例——Room类class Room: # 定义一个房间的类def __init__(self, n, o, l, w, h):self.name = nself.owner = oself.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 ()。
# 封装示例——Room类
class Room: # 定义一个房间的类def __init__(self, n, o, l, w, h):self.name = nself.owner = oself.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方法中实现,使功能增加,该如何改动代码?
# 封装示例——Room类
class Room: # 定义一个房间的类def __init__(self, n, o, l, w, h):self.name = n;self.owner = oself.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,参数列表)
#父类和子类示例class Person:def __init__(self,name,age):self.name = nameself.age = agedef 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_iddef 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()
输出结果:
您好!我是张三,今年33岁。
您好!我是李四,今年20岁。
我是学生,我的学号是:P171823001。
(3)类成员的继承和重写
通过继承,子类继承父类中除构造方法之外的所有成员。如果在子类中重新定义从父类继承的方法,则子类中定义的方法覆盖从父类中继承的方法。即:若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,这称之为方法重写。
程序案例:定义父类Dimension,包含2个数据成员x和y;定义继承于Dimension的两个子类Circle和Rectangle(Circle只有一个数据成员r),重写area方法输出信息。定义两个实例输出相关信息。
# 类成员的继承和重写示例
import math
class Dimension:def __init__(self, x, y):self.x = xself.y = ydef area(self):passdef 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.yd1 = Circle(2.0)
d2 = Rectangle(2.0, 4.0)
print(d1.area(), "\n", d2.area(),sep="")
d1.show_info()
d2.show_info()
输出结果:
12.57
8.0
Dimension类的show_info方法。
Dimension类的show_info方法。
(4)输出继承关系
多个类的继承可以形成层次关系,通过类的方法mro()或类的属性__mro__可以输出其继承的关系。例如:
# 输出继承关系示例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())
输出结果:
[<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语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。
# 类成员的继承和重写示例
import math
class Dimension:def __init__(self, x, y):self.x = xself.y = ydef area(self):passdef 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.yd1 = Circle(2.0)
d2 = Rectangle(2.0, 4.0)
print(d1.area(), "\n", d2.area(),sep="")
d1.show_info()
d2.show_info()
输出结果:
12.57
8.0
Dimension类的show_info方法。
Dimension类的show_info方法。
1.7.3 多态
多态是多种表现形态的意思,它是一种机制、一种能力,而非某个关键字。在面向对象的编程中,多态在类的继承和类的方法调用中得以体现。多态在不清楚对象的具体类型时,根据引用对象的不同而表现出不同的行为方式。当不知道对象到底是什么类型,但又要让对象“做点儿什么”的时候,都会用到多态。
对象可以表示多个类型的能力称为多态(Polymorphism)。多态性允许每个对象以自己的方式去响应共同的消息,从而允许用户以更明确的方式建立通用软件,提高软件开发的可维护性。
Python是一种动态语言,变量或参数无法也无需确定其类型。程序运行过程中,根据实际的对象类型确定变量的类型。
严格意义上讲,Python不支持多态性,即动态确定变量指向的对象类型。
(1)对象的多态表现
声明Dog类和Cat类,同时包括方法move,分别创建dog对象和cat对象,调用move方法,然后根据其类型的不同,它会表现出不同的行为。
# 对象的多态表现示例
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,输出飞快地跑!
输出结果:
<class '__main__.Dog'>
飞快地跑!
(2)函数和运算符的多态表现
# 函数和运算符的多态表现示例
def sum(a, b):return a + b
a = 1
b = 2
c = "深度"
d = "学习"
print(sum(a, b))
print(sum(c, d))
输出结果:
3
深度学习
(3) Python应用多态注意事项
Python和其他静态形态检查类的语言(如C++等)不同,在参数传递时不管参数是什么类型,都会按照原有的代码顺序执行,这就很可能会出现因传递的参数类型错误而导致程序崩溃的现象。
# 示例
def is_odd_number(a):return not a%2==0a = 3
print(is_odd_number(a)) # 输出:Trueb = 2.1
print(is_odd_number(b)) # 输出:True(没报错,但结果是错的)c = [3]
# print(is_odd_number(c))
# 输出:TypeError: unsupported operand type(s) for %: 'list' and 'int'
思考:怎样避免这种因传递的参数类型错误而导致程序崩溃的现象?
安全起见,需要自己编写代码来检验参数类型的正确性。
# 示例
def is_odd_number(a):if isinstance(a,int):return not a%2==0else:return -1 #类型不合法时返回-1a = 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个盘子按规则移动到右侧。
解题思路:
-
声明“塔柱”类,数据存储塔柱上的盘子(列表),方法包括向“塔柱”添加盘子(列表增加)、移去盘子(列表删除)、获取塔柱的高度(列表长度)以及最高处存放的盘子编号(列表最后一个值)等。
-
导入turtle库,创建并画出三个塔柱。同时创建塔柱类,并初始化第一个塔柱数据。
-
创建N个盘子(列表)。画出N个盘子,外形为方形,将盘子编号和外形尺寸对应起来。
-
移动汉诺塔,将数据层面的移动和图形化移动结合起来。注意,要先完成图形化移动,再实现数据层面移动。
递归移动方法:将n个盘子中的第n个看作为一个大盘,剩余n-1个盘子看作为一个小盘。先借助最右侧柱移动小盘到中间柱,再移动大盘到右侧柱,最后借助最左侧柱移动中间柱上的小盘到右侧柱。
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。传说大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
代码示例如下:
import turtle as t
from random import randomN = 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 pldef 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 pdef 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()
输出文字示例如下,动画效果自行尝试:
从0柱移动到2柱。
从0柱移动到1柱。
从2柱移动到1柱。
从0柱移动到2柱。
从1柱移动到0柱。
从1柱移动到2柱。
从0柱移动到2柱。
相关文章:

深度学习——第3章 Python程序设计语言(3.5 Python类和对象)
3.5 Python类和对象 目录 1. 面向对象的基本概念 2. 类和对象的关系 3. 类的声明 4. 对象的创建和使用 5. 类对象属性 6. 类对象方法 7. 面向对象的三个基本特征 8. 综合案例:汉诺塔图形化移动 1.1 面向对象的基本概念 1.1.1 对象(object&#x…...

【原创】【一类问题的通法】【真题+李6卷6+李4卷4(+李6卷5)分析】合同矩阵A B有PTAP=B,求可逆阵P的策略
【铺垫】二次型做的变换与相应二次型矩阵的对应:二次型f(x1,x2,x3)xTAx,g(y1,y2,y3)yTBy ①若f在可逆变换xPy下化为g,即P为可逆阵,有P…...

代码随想录算法训练营第六十天 | 84.柱状图中最大的矩形
84.柱状图中最大的矩形 题目链接:84. 柱状图中最大的矩形 本题与接雨水相近。按列来看,是要找到每一个柱子左右第一个比它矮的柱子,即对于该柱子来说所能组成的最大面积,将每个柱子所能得到的最大面积进行对比最终得到最大矩形。 …...

C#结合JavaScript实现多文件上传
目录 需求 引入 关键代码 操作界面 JavaScript包程序 服务端 ashx 程序 服务端上传后处理程序 小结 需求 在许多应用场景里,多文件上传是一项比较实用的功能。实际应用中,多文件上传可以考虑如下需求: 1、对上传文件的类型、大小…...

STM32——继电器
继电器工作原理 单片机供电 VCC GND 接单片机, VCC 需要接 3.3V , 5V 不行! 最大负载电路交流 250V/10A ,直流 30V/10A 引脚 IN 接收到 低电平 时,开关闭合。...

性能监控体系:InfluxDB Grafana Prometheus
InfluxDB 简介 什么是 InfluxDB ? InfluxDB 是一个由 InfluxData 开发的,开源的时序型数据库。它由 Go 语言写成,着力于高性能地查询与存储时序型数据。 InfluxDB 被广泛应用于存储系统的监控数据、IoT 行业的实时数据等场景。 可配合 Te…...

CS106L2023 and CS106B 环境配置(详细教程)
1.问题: (1)CS106L 运行./setup.sh 脚本时出错 (windows 请下载git,在git bash 打开运行) (2)CS106B,QT构建 构建错误:一般构建错误,例如 Erro…...

Docker-多容器应用
一、概述 到目前为止,你一直在使用单个容器应用。但是,现在您将 MySQL 添加到 应用程序堆栈。经常会出现以下问题 - “MySQL将在哪里运行?将其安装在同一个 容器还是单独运行?一般来说,每个容器都应该做一件事&#x…...

Golang导入导出Excel表格
最近项目开发中有涉及到Excel的导入与导出功能,特别是导出表格时需要特定的格式(单元格合并等),废话不多说,直接上代码了。 首先用到一个第三方库,实测还是很强大很好用的,就是这个https://git…...

基于Maven的Spring Boot应用版本号获取解析
引言 在Spring Boot应用的开发和部署中,了解应用的版本号对于管理和监控应用至关重要。本文将深入解析一种基于Maven打包的Spring Boot应用中,根据不同的运行环境获取应用版本号的解决方案。在开始介绍代码之前,我们先来了解一下可能的文件目…...

LLM微调(二)| 微调LLAMA-2和其他开源LLM的两种简单方法
本文将介绍两种开源工具来微调LLAMA-2。 一、使用autotrain-advanced微调LLAMA-2 AutoTrain是一种无代码工具,用于为自然语言处理(NLP)任务、计算机视觉(CV)任务、语音任务甚至表格任务训练最先进的模型。 1…...

AVP对纵向控制ESP(Ibooster)的需求规范
目录 1. 版本记录... 3 2. 文档范围和控制... 4 2.1 目的/范围... 4 2.2 文档冲突... 4 2.3 文档授权... 4 2.4 文档更改控制... 4 3. 功能概述... 5 4. 系统架构... 6 5. 主要安全目标... 7 5.1 …...

小模型学习(1)-人脸识别
【写作背景】因为最近一直在研究大模型,在与客户进行交流时,如果要将大模型的变革性能力讲清楚,就一定要能将AI小模型的一些原理和效果讲清楚,进而形成对比。当然这不是一件简单的事情,一方面大模型分析问题的的本质原…...

sublime Text使用
1、增加install 命令面板 工具(tool)->控制面板(command palette) -> 输入install ->安装第一个install package controller,以下安装过了,所以没展示 2、安装json格式化工具 点击install package,等几秒会进入控制面板࿰…...

基于深度学习的yolov7植物病虫害识别及防治系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介简介YOLOv7 系统特性工作流程 二、功能三、系统四. 总结 一项目简介 # YOLOv7植物病虫害识别及防治系统介绍 简介 该系统基于深度学习技术,采…...

Leetcode 2963. Count the Number of Good Partitions
Leetcode 2963. Count the Number of Good Partitions 1. 解题思路2. 代码实现 题目链接:2963. Count the Number of Good Partitions 1. 解题思路 这一题根据题意,显然我们可以将其先分为 n n n个原子partition,确保任意两个partition之间…...

C语言动态内存经典笔试题分析
C语言动态内存经典笔试题分析 文章目录 C语言动态内存经典笔试题分析1. 题目一2. 题目二3. 题目三4. 题目四 1. 题目一 void GetMemory(char *p){p (char *)malloc(100);} void Test(void){char *str NULL;GetMemory(str);strcpy(str, "hello world");printf(str)…...

截断正态分布stats.truncnorm()X.rvs(10000)
就是在均值和方差之外,再指定正态分布随机数群的上下限,如 [ μ − 3 σ , μ 3 σ ] [\mu-3\sigma,\mu3\sigma] [μ−3σ,μ3σ] stats.truncnorm()参数 X stats.truncnorm(-2, 2, locmu, scalesigma) -2 2是截断的正态分布…...

第59天:django学习(八)
事务 事务是MySQL数据库中得一个重要概念,事务的目的:为了保证多个SQL语句执行成功,执行失败,前后保持一致,保证数据安全。 开启事务的三个关键字 start transaction commit rollback 开启事务 from django.db import transaction…...

举例说明自然语言处理(NLP)技术。
本文章由AI生成! 以下是自然语言处理(NLP)技术的一些例子: 机器翻译:将一种语言翻译成另一种语言的自动化过程。常见的机器翻译系统包括谷歌翻译,百度翻译等。 语音识别:将口头语言转换成文本…...

echarts地图marker自定义图标并添加点击事件
symbol如果引用https图片链接会报403,直接引用本地 series: [{type: scatter, // 使用散点图系列 coordinateSystem: geo, // 设置坐标系为地理坐标系 zlevel: 100,data: [{name: 上海,value: [121.48, 31.22], // 上海的经纬度坐标 symbol: image:// require(/…...

C盘瘦身,C盘清理
以下只是我的C盘清理经验~ 一.【用软件简单清理C盘】 使用一些垃圾清理软件,简单的初步把C盘先清理一遍。(这种软件太多我就不推荐了……) 二.【WPS清理大师】 因为我电脑装了WPS,发现右键单击C盘有个选项【释放C盘空间】…...

STM32F103
提示:来源正点原子,参考STM32F103 战舰开发指南V1.3PDF资料 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 开发环境硬件普中科技,接…...

Unity使用打成图集的Sprite作为模型贴图使用的问题
大家好,我是阿赵。 有时候用Unity引擎做项目的时候,会遇到这样的需求,美术做了一些模型或者特效,然后策划想在游戏运行的时候,读取一些游戏图标放在特效或者模型上面当做贴图使用。 这个需求实现起来很简单&am…...

el-select赋值对象是对象时,出现赋值与展示不一致问题
代码逻辑类似:module 是个object { "appId": "", "id": 65, "name": "" } <el-form :model"form"><el-form-item label"申请模块" ><el-select v-model"…...

在 Node-RED 中引入 ECharts 实现数据可视化
Node-RED 提供了强大的可视化工具,而通过引入 ECharts 图表库,您可以更直观地呈现和分析数据。在这篇博客中,我们将介绍两种在 Node-RED 中实现数据可视化的方法:一种是引入本地 ECharts 库,另一种是直接使用 CDN&…...

docker资源限制
目录 系统压力测试工具stress 1. cpu资源限制 1.1 限制CPU Share 1.2 限制CPU 核数 1.3 CPU 绑定 2. mem资源限制 3. 限制IO 二、端口转发 三、容器卷 四、部署centos7容器应用 五、docker数据存储位置 六、docker网络 容器网络分类 在使用 docker 运行容器时&…...

探索HarmonyOS_开发软件安装
随着华为推出HarmonyOS NEXT 宣布将要全面启用鸿蒙原声应用,不在兼容安卓应用, 现在开始探索鸿蒙原生应用的开发。 HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 鸿蒙官网 开发软件肯定要从这里下载 第一个为微软系统(windows),第…...

CSS中控制元素水平布局的七个属性
元素的水平方向的布局 元素在其父元素中水平方向的位置由一下几个属性共同决定 margin-left border-left padding-left width padding-right border-right margin-right 一个元素在其父元素中,水平布局必须要满足以下…...

YOLOv8改进 | 2023检测头篇 | 利用AFPN改进检测头适配YOLOv8版(全网独家创新)
一、本文介绍 本文给大家带来的改进机制是利用今年新推出的AFPN(渐近特征金字塔网络)来优化检测头,AFPN的核心思想是通过引入一种渐近的特征融合策略,将底层、高层和顶层的特征逐渐整合到目标检测过程中。这种渐近融合方式有助于…...