首先声明!!!
1、此为知识点总结内容;
2、如有使用或转载请注明出处;
3、如有不足,欢迎批评指正;
* 学习路线
路线规划一(一般小学生):
对于能力一般的学员,36课就是他们的归宿,不用继续往下学;
36课之后,进入 百题速刷(数理解题) 、**海龟绘图(竞赛操作题)**两个篇章;
36课在1.5-2小时的线下课,可以填充15节左右。加上百题速刷和海龟绘图,可以填充30多节;
也就是说,这条路线完美解决48节一年课程;然后可以考虑考级。。。
不太建议第一年比赛,因为选择题部分,涉及到算法、数据结构等内容。。。
当然,由于操作题比较合理,而不太正规的赛事的理论题都是拿来主义,自己也不懂。。
大概率:给你一个题库,背一背就可以了。然后主攻操作题。
大部分学生其实无法进入更高阶段的学习,那继续 用Python 数理解题 和 海龟绘图 运作第二年;
路线规划二(优质小学生):
从37课后,可以继续学完,到最后。也可以按照路线一,然后再37课往后;
而本套课程学完之后,解题 和 绘图 也完毕了,考级也考了,比赛也比了;
后续就是 软件开发 和 游戏开发 的各种扩展主题课,直至到中学阶段;
路线规划三(优质中学生):
优质小学生开始进化优质中学生,更强的数理能力和逻辑能力,此时升级到C++信奥赛体系;
这里就开始规划一说的,正规赛事的教学和集训,开始涉及到:
数据结构(链表、堆栈、队列、二叉树等等)
图论(邻接矩阵、无相连通图、节点等)
算法(贪心、递推、二分、枚举、高精度、各种排序等等)
初等数学(代数、平面几何、数论、组合数学等)
当然,这是后话!!!
一、做个小软件.Tkinter入门
1. Tkinter初步
Tkinter是一款Python内置的GUI软件界面开发库,用这个库可以做软件
学习这款库,有以下注意点:
由于是针对小学生5年级+,不按成人那种手册式教学
也就是说:用到哪里,就只讲哪里,并且不涉及原理
如果对软件开发感兴趣,后续独立主题课会开设项目式课程
Tkinter和Pygame只花5-6节蜻蜓点水一下,做个了解,学个概念,跟做就行
系统性和软游项目课程会做独立课程,基础课中的内容学完啥也做不了的
tkinter库旗下的方法:
方法名
说明
.Tk()
生成窗体的方法,返回值是一个对象(点出万物的变量),设置当前窗体
.Label()
生成一个文字标签,返回值同上,设置当前文本
.mainloop()
循环窗体,不断刷新窗体内容
.Tk()方法下的方法:
方法名
说明
.geometry()
窗体的大小,长高坐标,具体传参看后续代码
.title()
窗体的标题,参数即标题名称
.Label()方法下的方法:
方法名
说明
.pack()
将文本内容定位到指定的窗体上,细节看后续代码
2. 代码详解
通过以上的知识点,完成一个包含文本的窗体
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 import tkinter as tkt = tk.Tk() t.geometry("800x600" ) t.title("tkinter 初步" ) lab1 = tk.Label(t, text="我的第一个tk程序!" ) lab1.pack() tk.mainloop()
二、交互.按钮和文本框
1. 按钮和文本框
按钮**.Button()组件方法和单行文本框 .Entry()**组件方法的使用
方法名
说明
.Button()
生成一个按钮,参数看后续代码
.Entry()
生成一个单行文本框,参数看后续代码
.Entry()旗下的方法:
方法名
说明
.get()
可以获取单行文本框的值
.delete()
可以删除单行文本框的值,清空直接范围:0-“end”
.Label()旗下的方法:
方法名
说明
.config()
可以修改文本内的值
如何实现按钮点击触发:
在Button()按钮内设置时,有一个属性:command
将一个函数fn赋值给command属性,即可完成点击按钮触发函数
2. 代码详解
通过以上的知识点,构建一个按钮和输入框,并实现交互
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ent1 = tk.Entry(t, font=("宋体" , 20 )) ent1.pack() def fn (): res = ent1.get() lab1.config(text=res) ent1.delete(0 , "end" ) but1 = tk.Button(t, text="确认" , command=fn) but1.pack()
三、合法性.验证和弹窗
1. 验证数据
可以通过以往的只是了判断输入框的数据是否符合要求
1 2 3 4 5 6 if res == "" : print ("数据不可以为空!" ) else : lab1.config(text=res) ent1.delete(0 , "end" )
2. 弹窗组件
弹窗组件是一个需要引入的独立库,通过这个组件可以实现弹窗效果
tkinter.messagebox库旗下的方法:
方法名
说明
.showinfo()
提示框的类型设定为:普通提示
.showwarning()
提示框的类型设定为:警告提示
.showinfo()和showwarning()方法旗下的属性:
方法名
说明
title
提示的标题
message
提示内容的详情文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import tkinter.messagebox as tmbif res == "" : tmb.showwarning(title="警告信息" , message="输入框不可以为空!" ) ent1.focus() else : lab1.config(text=res) ent1.delete(0 , "end" )
四、载入天空.绘图组件
1. 绘制天空
绘图组件:Canvas()提供了一些简单的绘图功能
方法名
说明
.Canvas()
在窗体上绘制图形,可以设置bg、width和height
.Canvas()方法旗下的方法:
方法名
说明
.create_oval(x1,y1,x2,y2[,options])
创建一个圆的图形,前四个参数为左上角和右下角坐标位置
.move(oval, moveX, moveY)
将绘图区的一个图形,移动相应坐标的数值
1 2 3 4 5 cv = tk.Canvas(t, bg="lightblue" , width="800" , height="300" ) oval = cv.create_oval(450 , 150 , 350 , 50 , fill="orange" ) cv.pack()
.Tk()方法旗下的方法:
方法名
说明
.resizeable(x,y)
设置窗体的x和y是否允许拖动,都设置False,即无法最大化
1 2 3 4 5 cv.move(oval, 0 , 100 ) t.resizable(False , False )
五、下拉的魅力.菜单组件
1. 设计菜单
软件有一个重要的功能,就是菜单系统:Menu()组件可以实现
tkinter库旗下的方法:
1 2 3 4 5 6 one = tk.Menu(t) file = tk.Menu(one, tearoff=0 )
.Menu()旗下的方法:
方法名
说明
.add_cascade()
在指定的菜单组中添加一个菜单项
.add_separator()
创建一条水平线
1 2 3 4 5 6 7 8 9 one.add_cascade(label="文件" , menu=file) file.add_command(label="新建" ) file.add_command(label="打开" ) file.add_separator() file.add_command(label="保存" ) file.add_command(label="关闭" , command=close)
.Tk()旗下的方法:
方法名
说明
.destroy()
关闭窗口
.config()
将组件展现
1 2 3 def close (): t.destroy()
2. 总结
对Tkinter库的一些说明
基础课程点到为止,学完了解即可,也无法真正开发软件(所需的知识点非常多的)
后续主题课会通过各种应用软件的开发给有兴趣的同学选择性系统学习
六、做个小游戏.Pygame入门
1. Pygame初步
Pygame库是一款专门用于游戏开发的库,用这个库可以做游戏
pygame库旗下的方法:
方法名
说明
.init()
初始化pygame模块及检查系统
.display
属性,控制窗口和屏幕的模块
.display属性旗下的方法:
方法名
说明
.set_mode()
设置窗体的大小,传递元祖参数,返回Surface模块
.set_caption()
属性,控制窗口和屏幕的模块
.flip()
刷新屏幕
.update()
刷新屏幕部分内容,在不传参时:同上
Surface模块旗下的方法:
2. 代码详解
窗口显示、背景色、标题等设置
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 import pygame as pgpg.init() screen = pg.display.set_mode((800 , 600 )) pg.display.set_caption("Pygame游戏" ) bgcolor = "pink" while True : screen.fill(bgcolor) pg.display.flip()
七、关掉它.event事件
1. 卸载操作quit()
初始化init()加载所需的各种模块,相对应的卸载就是quit()方法
2. event事件入门
事件:即需要通过触发才能执行的程序,比如鼠标点击、键盘按键等触发操作
事件如何触发执行?
需要不断循环刷新检测,因为鼠标键盘触发是随机时间触发的
事件类型也有不少,也需要每次把各种事件循环出来一一检测
pygame库旗下的属性(模块):
模块名
说明
.event
处理事件和事件列队的模块
.event事件模块下的方法:
.event.get()方法遍历返回值下的属性:
1 2 3 4 for e in pg.event.get(): print (e.type )
通过检测关闭窗口对应的事件值,然后卸载pygame
1 2 3 4 5 6 for e in pg.event.get(): if e.type == 256 : pg.quit()
pygame也提供了常量也对应常用的值:pg.QUIT(这个会返回256)
关闭代码编写完毕后,发现会报一个错误:原因是卸载后不认识了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 isOpen = True while isOpen: screen.fill(bgcolor) pg.display.flip() for e in pg.event.get(): if e.type == pg.QUIT: isOpen = False pg.quit()
八、移动的圆.绘制方法
1. 图形绘制
pygame也自带了各种绘图的方法,我们这里学习一下绘制一个圆以及矩形
pygame库旗下的属性(模块):
模块名
说明
.draw
绘图模块
.time
用于管理实践的模块
.draw模块下的方法:
方法名
说明
.rect()
绘制一个矩形,参数参考代码
.circle()
绘制一个圆,参数参考代码
.time模块下的方法:
方法名
说明
.Clock()
可以返回一个追踪时间的变量(对象)
.Clock()方法下的方法:
2. 代码详情
绘制一个静态的矩形,绘制一个可以移动的圆,并设置刷新频率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 clock = pg.time.Clock() x, y = 390 , 290 while isOpen: clock.tick(10 ) pg.draw.rect(screen, ("blue" ), (100 , 100 , 35 , 35 )) pg.draw.circle(screen, ("red" ), (x, y), 20 ) x += 20
九、键盘控制.事件监听
1. 键盘监听
本节课想要通过用户键盘的操作,来改变小球的移动方向
e.type可以获取到事件的类型:
键盘按下时返回值是:768
键盘弹起时返回值是:769
可以直接判断:e.type == 768
或者pygame提供了常量pg.KEYDOWN值为:768
1 2 elif e.type == pg.KEYDOWN:
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 dir = "right" isOpen = True while isOpen: if dir == "right" : x += 20 elif dir == "left" : x -= 20 elif dir == "up" : y -= 20 elif dir == "down" : y += 20 for e in pg.event.get(): if e.type == pg.QUIT: isOpen = False elif e.type == pg.KEYDOWN: if e.key == pg.K_LEFT: dir = "left" elif e.key == pg.K_RIGHT: dir = "right" elif e.key == pg.K_UP: dir = "up" elif e.key == pg.K_DOWN: dir = "down"
十、碰撞检测.坐标重叠
1. 坐标重叠
我们想让移动的球对方块进行碰撞,采用坐标重叠的方法设计
如果和判断两个图形重叠:
得到方块的坐标位置为:x=>100, y=>100
加上方块本身的大小35,算出占用的区域:x(100~135), y(100~135)
有些判断希望擦边一点点不算碰撞,那可以设计一个值让判断更加中心一点
1 2 3 4 5 6 7 8 9 10 11 12 13 rx, ry = 100 , 100 while isOpen: pg.draw.rect(screen, ("blue" ), (rx, ry, 35 , 35 )) if rx != -9999 and ry != -9999 : if x > rx and x < rx + 35 and y > ry and y < ry + 35 : rx = -9999 ry = -9999
1 2 3 if x < 0 + 20 or x > 800 - 20 or y < 0 + 20 or y > 600 - 20 : x, y = 390 , 290
十一、计算得分.输入文本
1. 文字输入
pygame库提供了文字输入的模块方法font
pygame库旗下的属性(模块):
.font模块下的方法:
方法名
说明
.Sysfont()
返回一个可以创建字体的变量(对象Font)
1 2 3 4 font = pg.font.SysFont("simhei" , 20 )
.Sysfont()方法下方法:
Surface模块下的方法:
方法名
说明
.blit()
将图像或文字图形绘制到窗体上
1 2 3 4 5 6 7 screen.blit(font.render("得分:" , True , ("black" )), (720 ,10 )) screen.blit(font.render(str (score), True , ("black" )), (780 , 10 ))
1 2 3 4 5 6 7 8 9 10 score = 0 if rx != -9999 and ry != -9999 : score += 1 if x < 0 + 20 or x > 800 - 20 or y < 0 + 20 or y > 600 - 20 : score = 0
十二、总裁思维.面向对象
1. 面向过程
之前学习的编程方式就是面向过程,即员工思维。
**提醒:面向对象难度较大,对于教培老师,当然要掌握;对于小学生,可选;**
所谓员工思维:
按部就班的完成每一个步骤:比如上班:
1.起床 2.洗脸 3.坐公交 4.打卡进公司 5.工作 …等
用程序代码理解:自上而下按顺序的一步步完成既定的代码程序;
2. 面向对象
面向对象就好比总裁思维,那总裁的思维是哪些?
所谓总裁思维:
按需分配让他人执行所要的步骤:比如上班:
用程序代码理解:以调用的方式,给各个模块发送指令,并执行;
3. 属性和方法
我们之前已经零星的接触过属性和方法的区别
属性和方法概念:
方法其实就是函数,只不过有一个点语法:pg.init()
方法的其实就是:对象.方法() ,一般用于执行某种行为动作
而属性,就是不带括号的:e.type
属性语法为:对象.属性 ,一般用于获取某种特性的值
备注:我们之前点出万物的变量都可以理解为对象
实例演练,小汽车对象:
它有哪些属性呢?
它有哪些方法呢?
获取特性的是属性,执行动作的是方法
十三、泰拉瑞亚.类与对象
1. 生成概念
有玩过泰拉瑞亚的世界生成形式的游戏吗?大概这个意思
理解上图:
世界模型可以理解为基石(一切的基础),也可以理解为生产世界的工厂,也是后面“类”这个概念;
生成世界123,即:世界工厂生产的行为,英文为new,新建,创建的意思;
各种特性的副本,即:对象,对象是具有各种属性和方法的,比如:
火山熔岩副本,具有热、灼烧的属性,需要有降温、灭火等方法
寒冰洞窟副本,具有冷、冰冻的属性,需要有保暖、碎冰等方法
沙漠戈壁副本,。。。。。
也就是说,类可以包含全部的功能,而生成对象的时候呢,随机选择特性载入;
下面就需要通过代码来实现这个类和对象。
2. 创建类和对象
类是一切的基石,而对象是类生成出来的一个副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class World : pass w = World() print (w)print (type (w))
十四、载入特性.创建属性
1. 初始化函数
初始化函数:__ init __() 一般在定义类时定义,也可以称为初始化方法
初始化函数作用:
如果把类理解成函数,当实例化(创建对象)时,会自动执行初始化函数;
初始化函数,也叫初始化方法,在别的语言称为构造方法,这里也可以;
参数1是必填参数,固定关键字self,调用本类里的属性和方法用的;
那么它的作用到底是什么呢?很简单:在创建对象时自动执行,初始化各项数据;
1 2 3 4 5 6 7 8 9 class World (): def __init__ (self ): pass w = World()
1. 类的属性
类的属性,一般用于保证创建的对象具有相应的特性
那么,如何创建三个不同特性的世界副本呢?通过初始化函数试试
1 2 3 4 5 6 7 8 9 10 11 12 13 class World (): def __init__ (self ): self.name = "火山" self.gender = "熔岩" w1 = World() print (w1.name+w1.gender,"的世界被创建!" )
以上的代码只能创建有一个特性的世界副本。如何创建不同的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class World (): def __init__ (self, name, gender ): self.name = name self.gender = gender w1 = World("火山" , "熔岩" ) print (w1.name+w1.gender,"的世界被创建!" )w2 = World("寒冰" , "洞窟" ) print (w2.name+w2.gender,"的世界被创建!" )
结构分析:
每个对象都是内存独立分配的空间;
每个对象的属性,都是独立的,和其它对象属性毫无关系;
十五、固有特性.静态属性
1. 静态概念
所谓静态,无法实例化改变,数据定义后可以共享给所有对象使用
所谓静态属性:
静态属性,直接在类的顶部定义,无法通过对象改变的属性;
静态属性可以用一个特殊的语法:类.静态属性 ,而无需实例化对象;
什么样的环境需要静态:所有对象的某个属性特性都是同一个值,那就可以静态(共享);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class World : age = 1000 def __init__ (self, name, gender ): self.name = name self.gender = gender w1 = World("火山" , "熔岩" ) print (w1.age)w2 = World("寒冰" , "洞窟" ) print (w2.age)print (World.age)
2. 动态属性
动态属性,就很好理解了。直接在实例化对象创建的属性
1 2 3 4 5 6 w1 = World("火山" , "熔岩" ) print (w1.age)w1.info = "我是动态属性" print (w1.info)
名词解释:
创建属性:一般是在类里声明的属性,比如 self.name 这种;
实例属性:实例化后对象调用的属性,比如 w1.name 这种;
静态属性:可以用类直接调用,也可以对象调用的属性:World.age 这种;
动态属性:类里面没有定义的属性,在实例化后对象直接声明的属性:w1.info 这种;
十六、行动激活.创建方法
1. 实例方法
实例方法(函数),也是普通类里的方法,用于执行实例化对象后的某种行为
1 2 3 4 5 6 7 8 9 10 def run (self ): return "遇到火山熔岩副本,我要使用降温灭火装备!" w1 = World("火山" , "熔岩" ) print (w1.run())w2 = World("寒冰" , "洞窟" ) print (w2.run())
如果要实现针对不同对象(副本),采取对应的行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class World : age = 1000 def __init__ (self, name, gender, a, b ): self.name = name self.gender = gender self.a = a self.b = b def run (self ): return "遇到" + self.name + self.gender +\ "副本,我要使用" + self.a + self.b + "装备!" w1 = World("火山" , "熔岩" , "降温" , "灭火" ) print (w1.run())w2 = World("寒冰" , "洞窟" , "保暖" , "碎冰" ) print (w2.run())
2. 静态方法
和静态属性一样,静态方法是每一个对象(副本)都可以共享使用的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @staticmethod def over (): return "Game Over!" w1 = World("火山" , "熔岩" , "降温" , "灭火" ) print (w1.run())print (w1.over())w2 = World("寒冰" , "洞窟" , "保暖" , "碎冰" ) print (w2.run())print (w2.over())print (World.over())
3. 动态方法
和动态属性一样,在类外声明的普通函数,并用实例化对象调用的方法
1 2 3 4 5 6 7 def eat (): return "吃饭" w2.eat = eat print (w2.eat())
4. 类方法
使用装饰器@classmethod声明的方法
类方法:
和静态方法一样,支持 类.方法() 的调用方式;
静态方法只处理数据,无法获取本类的数据,比如无法使用self;
类方法必须传递 cls 参数,通过这个 cls 来调用本类的静态属性等;
1 2 3 4 5 6 7 @classmethod def info (cls ): return cls.age print (World.info())
十七、私有属性.封装
1. 封装性
面向对象有三大特性:封装、继承和多态,那什么是封装?
封装的解释:
对属性进行私有化操作,这种行为就是封装;
为何要封装,防止这些属性被污染;
怎么理解污染?想一下电脑机箱,为何要把主板、内存、显卡封在壳子里,只留个插孔;
我们先看下公有属性,如何赋值和取值的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class World : def __init__ (self, name, gender ): self.name = name self.gender = gender w1 = World("火山" , "熔岩" ) w1.name = "沙漠" print (w1.name)
1 2 3 4 5 6 7 8 def __init__ (self, name, gender ): self.__name = name self.__gender = gender w1 = World("火山" , "熔岩" ) print (w1.__name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class World : def __init__ (self, name, gender ): self.__name = name self.__gender = gender def getName (self ): return self.__name w1 = World("火山" , "熔岩" ) print (w1.getName())
封装私有化赋值,有时不是在实例化后赋值的,可以再建立一个赋值入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class World (): def __init__ (self, name = "" , gender = "" ): self.__name = name self.__gender = gender def setName (self, name ): self.__name = name def getName (self ): return self.__name w1 = World() w1.setName("沙漠" ) print (w1.getName())
2. 私有方法
私有方法和私有属性概念一样
1 2 3 4 5 6 def __run (self ): return "运行。。。" print (w1.__run())
1 2 3 4 5 6 7 8 9 10 def __run (self ): return "运行。。。" def go (self ): return self.__run() print (w1.go())
十八、装饰器.@property
1. @property
getName() 和 setName() 是一种比较通用的封装属性的手段,但还是有点麻烦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class World : def __init__ (self, name = "火山" , gender = "熔岩" ): self.__name = name self.__gender = gender def getName (self ): return self.__name w1 = World() w1.__name = "寒冰" print (w1.__name)print (w1.getName())
问题分析:
私有属性赋值有两种,一种就是 初始化函数传参 ,第二种就是 setName() 方法;
我们希望属性在对象这里,直接用属性赋值取值,可读性更好一些;
所以,需要使用装饰器@property来完成这种功能,具体如下:
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 class World : def __init__ (self, name = "火山" , gender = "熔岩" ): self.__name = name self.__gender = gender @property def name (self ): return self.__name @name.setter def name (self, name ): self.__name = name def getName (self ): return self.__name w1 = World() w1.name = "寒冰" print (w1.name)print (w1.getName())
十九、第二大特性.继承
1. 继承概念
父亲有100w,儿子继承了父亲的100w,那么儿子就有了100w,这就是继承
继承扩展理解:
世界类或世界工厂,在生成火山、寒冰、沙漠副本时,逻辑都在本类或本工厂里;
当这些副本需求内容过多,或逻辑逐步复杂时,世界工厂内的代码将变得冗余难以理解;
尤其是当需要通过实例化调用时传递不同参数时,也会变成杂乱可读性差;
那么,我们将这个工厂开始拆分,分为三个工厂:火山工厂、寒冰工厂、沙漠工厂;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Fire : name = "火山" gender = "熔岩" def fight (self ): print ("在" + self.name + self.gender +"里战斗!" ) class Cold : name = "寒冰" gender = "洞窟" def fight (self ): print ("在" + self.name + self.gender +"里战斗!" ) f = Fire() f.fight() c = Cold() c.fight()
结构分析:
模型拆分后,逻辑上清晰很多,不用在一个类中设计太多不同副本的功能;
初始化函数的传递参数上,直接被静态属性取代了;
但暴露出一个新的问题,每个副本都有相同的行为和属性怎么办?
比如,这里 fight() 战斗方法,由于拆分后,又冗余了;
二十、复制共性.改写特性
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 26 27 28 class World : age = 1000 def fight (self ): print ("在" + self.name + self.gender +"里战斗!" ) class Fire (World ): name = "火山" gender = "熔岩" class Cold (World ): name = "寒冰" gender = "洞窟" f = Fire() print (f.age)f.fight() c = Cold() c.fight()
2. 改写特性
有时,我们需要修改父类继承下来的属性和方法,以及初始化函数
初始化函数:
上面为了方便理解,均用了静态属性;
本次我们尝试用初始化函数的声明的属性试一试;
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 class World : age = 1000 def __init__ (self, name, gender ): self.name = name self.gender = gender def fight (self ): print ("在" + self.name + self.gender +"里战斗!" ) class Fire (World ): pass class Cold (World ): pass f = Fire("火山" , "熔岩" ) print (f.age)f.fight() c = Cold("寒冰" , "洞穴" ) c.fight()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Fire (World ): def __init__ (self ): World.__init__(self, "火山" , "熔岩" ) class Cold (World ): def __init__ (self ): World.__init__(self, "寒冰" , "洞穴" ) f = Fire() print (f.age)f.fight() c = Cold() c.fight()
1 2 3 4 5 def fight (self ): World.fight(self) print (123 )
二十一、继承中的抽象化
1. 抽象化
对于父类,我们有几个问题有待解决,比如父类要不要封装等
对于继承的思考:
首先,父类能不能实例化,答案:现在能,但我们希望不能;
因为,我们只是希望使用子类创建对象副本,父类只是提供规范;
或者,用好理解的语言表述:总裁只是下达命令,真正执行的是员工;
员工按照命令去执行即可,但员工自然也有自己独有的特性罢了;
所以,在父类中,有些属性和方法,我们只希望提供规范让子类实现:
创建一个抽象的世界类,防止用户实例化:
1 2 3 4 5 6 7 8 9 10 11 12 import abcclass World (metaclass=abc.ABCMeta): @abc.abstractmethod def fight (self ):pass w1 = World()
创建一个子类去继承抽象父类,实例化后强制要求实现抽象方法:
1 2 3 4 5 6 7 class Fire (World ): pass f = Fire()
1 2 3 4 5 6 7 8 class Fire (World ): def fight (self ): return "战斗!" f = Fire() print (f.fight())
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 import abcclass World (metaclass=abc.ABCMeta): @property @abc.abstractmethod def name (self ):pass @abc.abstractmethod def fight (self ):pass class Fire (World ): @property def name (self ): return self.__name @name.setter def name (self, name ): self.__name = name def fight (self ): return "在" + self.__name + "区域战斗!" f = Fire() f.name = "火山" print (f.fight())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from abc import ABCMeta, abstractmethodclass World (metaclass=ABCMeta): @property @abstractmethod def name (self ):pass @abstractmethod def fight (self ):pass
二十二、第三大特性.多态
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 26 27 28 29 30 31 32 33 34 35 36 37 38 from abc import ABCMeta, abstractmethodclass World (metaclass=ABCMeta): @abstractmethod def fight (self ):pass class Fire (World ): def fight (self ): print ("与炎魔战斗!" ) class Cold (World ): def fight (self ): print ("与霜魔战斗!" ) def fight (obj ): obj.fight() fight(Fire()) fight(Cold()) ''' f = Fire() c = Cold() fight(f) fight(c) '''
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class World : def fight (self, obj ): obj.fight() class Fire (World ): def fight (self ): print ("与炎魔战斗!" ) class Cold (World ): def fight (self ): print ("与霜魔战斗!" ) w = World() w.fight(Fire()) w.fight(Cold())
好处:当有非常多的副本,但他们通用的功能是战斗,而战斗的细节又不同,就避免大量的if等判断操作。
二十三、面向对象工具箱
1. 工具箱合集
面向对象中,有很多工具方法来更方便的查询或构建我们的代码
一般带有前后两个下划线的方法,称为魔术方法:
__ init __ () :初始化函数,或称为构造方法,当实例化时
__ str __ () :打印对象时,替代原本的描述信息
__ del __() :析构函数,和 初始化 正好相反,当删除实例化对象时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class World : def __init__ (self, x, y ): self.x = x self.y = y def __str__ (self ): return "这是一个世界类" + str (self.x) def __del__ (self ): print ("删除对象时,我被触发" + str (self.y)) w = World(10 , 20 ) print (w)del w
2. super()函数
重写父类方法时,我们还需要得到父类方法最终的结果,使用super()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class World : def run (self ): return "World!" class Fire (World ): def run (self ): return super ().run() + "Fire!" f = Fire() print (f.run())
二十四、实训.塞尔达传说①
1. 需求分析
从本节课开始,我们用面向对象实战一款经典的开放世界文字版:塞尔达传说
具体要求如下:
游戏开始,醒来,系统让你输入用户名;
当然,不管你输入什么(除了林克),系统都会纠正你叫“林克”;输入正确,不会纠正;
你走出洞穴,生命体只有1颗星,遇到烤火老爷爷,要给你两个烤苹果;
你接受吃了烤苹果,则变成3颗星,你走入寒冰区,老爷爷要给你防寒服,你可以选择;
防寒服可以增加你在本区域的防御值,你原本的防御值为1,遇到寒冰怪,血量为1,它先手;
对战胜利或失败,失败退出游戏;胜利后,进入火山区,矿工要给你防火服,同上;
然后,走入城堡拯救公主,公主介绍Boss,攻击力10000,防御力4000,生命值5000,它先手;
她说可以给你一把攻击力8999的光之弓,还加送一张先手祝福之卷轴;胜利或失败;
2. 初始化数据
直接面向对象,对于新手来说不太容易,先面向过程开始,从底层打工开始
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 print ("Loading..." )print ("Loading..." )print ("Loading..." )print ("游戏开始...." )print ("林克,林克,林克,醒一醒...." )name = input ("请输入你的名字:" ) if name != "林克" : print ("你忘了吗?你叫林克!" ) name = "林克" life = 1 print ("\n走出洞穴,遇到一个老爷爷,他要送你两个烤苹果!" )flag = int (input ("是否接受?接受输入->1,不接受输入->0:" )) if flag == 1 : life += 2 print ("你的生命值+2" ) defence = 1 attack = 1 print ("\n目前生命值:%d,防御力:%d,攻击力:%d" %(life, defence, attack))
二十五、实训.塞尔达传说②
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 print ("\n走入寒冰区,老爷爷要送你防寒服!" )defence_cold = 0 flag = int (input ("是否接受?接受输入->1,不接受输入->0:" )) if flag == 1 : defence_cold = defence + 1 print ("你的寒冰区防御力+1" ) print ("\n你遇到了寒冰怪,开始挑战!" )enemy1_atk = 2 enemy1_life = 1 while True : life = life - (enemy1_atk - defence_cold) if life <= 0 : print ("挑战失败!" ) print ("Game over!" ) break enemy1_life = enemy1_life - attack if enemy1_life <= 0 : print ("你胜利了!目前生命值为:%d" %life) break print ("\n胜利后继续走入火山区" )print ("\n目前生命值:%d,防御力:%d,攻击力:%d" %(life, defence, attack))
2. 退出机制
胜利后可继续往下执行,但失败后,要退出整个系统,需要函数处理
1 2 3 4 5 6 7 8 def main (): ... main()
二十六、实训.塞尔达传说③
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 26 27 28 29 30 31 32 33 34 35 print ("\n火山区矿工告诉你,里面很热,需要防火服" )defence_fire = 0 flag = int (input ("是否接受?接受输入->1,不接受输入->0:" )) if flag == 1 : defence_fire = defence + 1 print ("你的火山区防御力+1" ) print ("\n你遇到了熔岩怪,进入战斗模式!" )enemy2_atk = 2 enemy2_life = 1 while True : life = life - (enemy2_atk - defence_fire) if life <= 0 : print ("挑战失败!" ) print ("Game over!" ) return enemy2_life = enemy2_life - attack if enemy2_life <= 0 : print ("你胜利了!目前生命值为:%d" %life) break print ("\n胜利后走入城堡迎接公主" )print ("\n目前生命值:%d,防御力:%d,攻击力:%d" %(life, defence, attack))
2. Boss登场
介绍Boss和初始化数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 print ("\n公主:我的骑士啊!恶魔的攻击力为10000,防御力4000,生命值5000,而且先手!" )print ("我这里有一把攻击力为8999光之弓,还有一张先手祝福之卷轴!" )flag1 = int (input ("是否接受光之弓?接受输入->1,不接受输入->0:" )) flag2 = int (input ("是否接受先手祝福之卷轴?接受输入->1,不接受输入->0:" )) enemy3_life = 5000 enemy3_defence = 4000 enemy3_atk = 10000 if flag1 == 1 : attack += 8999
二十七、实训.塞尔达传说④
1. Boss对战
Boss对战和小怪基本一致,主要是否接受先手Buff这个条件
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 while True : if flag2 == 1 : enemy3_life = enemy3_life + enemy3_defence - attack flag2 = 0 if enemy3_life <= 0 : print ("\n你战胜了恶魔,拯救了公主,游戏通关!" ) return life = life - (enemy3_atk - defence) if life <= 0 : print ("你被恶魔打败啦,公主也没救回来!" ) print ("Game over!" ) return enemy3_life = enemy3_life + enemy3_defence - attack if enemy3_life <= 0 : print ("\n你战胜了恶魔,拯救了公主,游戏通关!" ) return
员工的编码思维已经做完,后面要改成面向对象的方式。
二十八、实训.塞尔达传说⑤
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 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 53 class Aside : @staticmethod def start (): print ("Loading..." ) print ("Loading..." ) print ("Loading..." ) print ("游戏开始...." ) print ("林克,林克,林克,醒一醒...." ) @staticmethod def boss (): print ("\n公主:我的骑士啊!恶魔的攻击力为10000,防御力4000,生命值5000,而且先手!" ) print ("我这里有一把攻击力为8999光之弓,还有一张先手祝福之卷轴!" ) @staticmethod def win (): print ("你战胜了恶魔,拯救了公主,游戏通关!" ) @staticmethod def who (name ): print ("你被%s打败啦,公主也没救回来!" %name) @staticmethod def over (): print ("Game over!" ) @staticmethod def fight (name ): print ("\n你遇到了%s,进入战斗模式!" %name) @staticmethod def single (name ): print ("恭喜你战胜了%s!" %name) @staticmethod def status (life, defence, attack ): print ("目前生命值:%d,防御力:%d,攻击力:%d" %(life, defence, attack)) @staticmethod def input (info ): return input ("是否接受%s?接受输入->1,不接受输入->0:" %info)
然后,在主程序用静态方法来调用这些归类好的方法即可:
1 2 3 4 5 6 7 8 Aside.start() Aside.who(name) Aside.over()
二十九、实训.塞尔达传说⑥
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 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 53 54 55 56 57 58 class Person : def __init__ (self, name, life, defence, attack ): self.__name = name self.__life = life self.__defence = defence self.__attack = attack self.__defence_cold = 0 self.__defence_fire = 0 @property def name (self ): return self.__name @name.setter def name (self, name ): self.__name = name @property def life (self ): return self.__life @life.setter def life (self, life ): self.__life = life @property def defence (self ): return self.__defence @defence.setter def defence (self, defence ): self.__defence = defence @property def attack (self ): return self.__attack @attack.setter def attack (self, attack ): self.__attack = attack @property def defence_cold (self ): return self.__defence_cold @defence_cold.setter def defence_cold (self, defence_cold ): self.__defence_cold = defence_cold @property def defence_fire (self ): return self.__defence_fire @defence_cold.setter def defence_fire (self, defence_fire ): self.__defence_fire = defence_fire
然后通过Aside开场时,生成人物对象,在主程序操作人物数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @staticmethod def start (): print ("Loading..." ) print ("Loading..." ) print ("Loading..." ) print ("游戏开始...." ) print ("林克,林克,林克,醒一醒...." ) name = input ("请输入你的名字:" ) if name != "林克" : print ("你忘了吗?你叫林克!" ) name = "林克" p = Person(name, 1 , 1 , 1 ) return p
主程序,涉及到人物的六个属性时,全部加上对象.属性 的格式:
1 2 3 4 5 p.life = p.life - (enemy1_atk - p.defence_cold) Aside.status(p.life, p.defence, p.attack)
三十、实训.塞尔达传说⑦
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 26 27 28 29 30 31 class Enemy : def __init__ (self, life, attack, defence = 0 ): self.__life = life self.__attack = attack self.__defence = defence @property def life (self ): return self.__life @life.setter def life (self, life ): self.__life = life @property def attack (self ): return self.__attack @attack.setter def attack (self, attack ): self.__attack = attack @property def defence (self ): return self.__defence @defence.setter def defence (self, defence ): self.__defence = defence
主程序,涉及到怪物的三个属性时,全部加上对象.属性 的格式:
1 2 3 4 5 en1 = Enemy(1 , 2 ) p.life = p.life - (en1.attack - p.defence_cold)
三十一、实训.塞尔达传说⑧
1. 战斗类
将小怪和boss战的逻辑,存储到一个单独的类进行管理
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 53 54 55 56 57 class Fight : @staticmethod def general (p, en, Aside, defence ): while True : p.life = p.life - (en.attack - defence) if p.life <= 0 : Aside.who(en.name) Aside.over() return False en.life = en.life - p.attack if en.life <= 0 : Aside.single(en.name) return True @staticmethod def boss (p, en, Aside, flag2 ): while True : if flag2 == 1 : en.life = en.life + en.defence - p.attack flag2 = 0 if en.life <= 0 : Aside.win() return False p.life = p.life - (en.attack - p.defence) if p.life <= 0 : Aside.who(en.name) Aside.over() return False en.life = en.life + en.defence - p.attack if en.life <= 0 : Aside.win() return True
1 2 3 4 5 6 7 8 9 10 11 12 13 en2 = Enemy("熔岩怪" , 1 , 2 ) Aside.fight(en2.name) if Fight.general(p, en2, Aside, p.defence) == False : return en3 = Enemy("恶魔" , 5000 , 10000 , 4000 ) if Fight.boss(p, en3, Aside, flag2) == False : return
三十二、实训.塞尔达传说⑨
1. 场景类
目前四个场景都在主程序中,分成独立类,方便以后扩展升级
在main.py中,执行主函数,已经只是调用关系,根据场景一一调用,非常清晰
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 from Aside2 import *from Original2 import *from Cold2 import *from Fire2 import *from Castle2 import *def main (): p = Aside.start() o = Original() o.exec (p, Aside) c = Cold() c.exec (p, Aside) f = Fire() f.exec (p, Aside) cc = Castle() cc.exec (p, Aside)
创建世界的抽象父类,在基础知识部分学习过,其它四个场景,则继承它
1 2 3 4 5 6 7 8 9 from abc import ABCMeta, abstractmethodfrom Enemy2 import *from Fight2 import *class World (metaclass=ABCMeta): @abstractmethod def exec ():pass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from World2 import *class Original (World ): def exec (self, p, Aside ): print ("\n走出洞穴,遇到一个老爷爷,他要送你两个烤苹果!" ) flag = int (Aside.input ("烤苹果" )) if flag == 1 : p.life += 2 print ("你的生命值+2" ) Aside.status(p.life, p.defence, p.attack)
其余的三个子类,一模一样,不再贴代码。
这么做的好处显而易见,扩展场景,只需要在这个类中设计即可
三十三、实训.塞尔达传说⑩
1. 启动主程序
观察目前结构,调整重复代码,并实现场景的多态调用,实现总裁式一行启动模式
在World类中,增加一个统计数据的方法,来避免每个子类重复操作
1 2 3 4 def stats (self, p ): Aside.status(p.life, p.defence, p.attack)
Aside类,直接通过World引入,避免前台传递Aside类
在World类中,创建一个静态方法,实现多态的调用效果
由于return 被封装到子类的方法里了,无法实现退出函数,需要再传递回来
1 2 3 4 @staticmethod def run (obj, p ): return obj.exec (p)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def main (): p = Aside.start() World.run(Original(), p) if World.run(Cold(), p) == False : return if World.run(Fire(), p) == False : return if World.run(Castle(), p) == False : return
1 2 if Fight.general(p, en1, Aside, p.defence_cold) == False : return False
1 2 3 4 5 import game2 as gg.main()