class
02. Python导言须知.软件使用
1. 导言须知
-
课程方向:
-
知识点主要面向小学5年级+的少儿编程Python课程,适用于各类竞赛;
-
课程比较适合理工科父母的亲子互动,教培机构老师备课参考;
-
-
课程规划:
- 1.基础篇:输入输出函数、变量及数据类型、条件判断、循环、函数、字符串、列表元祖字典、常用库;
- x.数学篇:独立课程,采用OJ系统刷题教学;
- 2.绘图篇:Turtle海龟绘图库,基础教学在系统课程里;
- y.绘图扩展篇:Turtle海龟绘图库的提高扩展部分,面向竞赛的独立课程;
- 以上部分,止步于小学5年级+,OJ刷题如需要更高年级或能力则会特别说明;
- 以下部分,将大于小学5年级+,或需要更高能力者;
- 3.面向对象篇:OOP的设计思维以及类的多种方式;
- 4.Tkinter篇:GUI可视化模块,软件开发初步;
- 5.Pygame篇:入门级游戏库,游戏开发初步;
- z.项目篇:独立课程,每个项目一个课程,常用软件和游戏开发课程;
特别说明:竞赛中理论部分会有大量本课程无法涉及的部分:比如进制问题(二进制、八进制等)、数据结构和算法部分(二叉树、排序、链表等)以及初等数学、离散、图论等均没有。
2. 软件使用
3. print()输出函数
# 向控制台输出一行字:Hello, Python
# 注意1:括号和引号(单双均可)必须是英文状态下的
# 注意2:#号开头表示注释,它的作用是解释,并不会执行
print("Hello, Python!")
03. 神奇的存储罐.变量
1. 变量的声明
- 什么是变量?变量就是一个可以存储东西的存储罐,具体特性如下:
- 每次存储罐里只能存一次东西,也就是说再次存储时,上一次存储的东西会被替换掉;
- 声明变量就好比你拿出了一个存储罐,实际上是在内存里开辟了一个空间;
- 不管是现实中的存储罐还是内存开一个空间,多了防止混淆就需要贴个标签,命个名;
- 我们来通过一张图来理解一下,这个声明概念:
那么,如何通过编程来实现变量的声明呢?
# 声明一个变量
# 注意1:= 不是等于的意思,是赋值的意思
# 注意2:阅读代码从右边向左边阅读,意为:将数字123赋值给变量a
a = 123
# 输出a
print(a)
# a变量被替换
a = 456
print(a)
2. 命名的规则
-
从 a=123 的例子中,a是变量名,但变量的名字有什么要求?需要注意哪些规则?能乱起吗?
-
变量名可以包含字母、数字、下划线,但数字不能在变量名首位;
- abc(合法)、a123(合法)、_xyz(合法)、123a(不合法)
-
Python中的关键字无法作为变量名,编辑器会提示,比如:def、class,使用会直接报错;
-
Python中的英文是区分大小写的,大写的A和小写的a不是一种东西;
-
-
为了让变量名更加的有可读性,比如:你的名字,当然,中文变量名也是支持的,但基本不用;
- 第一种经典命名方式,驼峰式:yourName,或YourName,我比较喜欢前者;
- 第二种经典命名方式:蛇形式:your_name,用的也比较多;
# 关键字错误命名
def = 123
class = 123
print(class)
# 中文命名
你的名字 = 123
print(你的名字)
# 驼峰式
yourName = 123
print(yourName)
# 蛇形式
your_name = 123
print(your_name)
3. Shell模式
有时我们要对一些结果进行快速获取,可以直接使用命令行窗口直接输出:Shell模式;
>>> a = 123
>>> a
123
>>> b = 456
>>> print(b)
456
# 如果在主程序本身就已经运行赋值了,直接在命令行输出也是可以的
>>> c
798
03. 来做四则运算吧.算术
1. 算术运算符
虽说叫做四则运算,但其实运算符往往不止加减乘除这四种:
运算 | 符号 | 格式 |
---|---|---|
加 | + | 1 + 1 |
减 | - | 2 - 1 |
乘 | * | 3 * 4 |
除 | / | 6 / 2 |
整除 | // | 9 // 7 |
求余 | % | 10 % 8 |
幂 | ** | 2**3 |
- 在编程语法中乘法 x 用 ***** 号代替,除法 ÷ 用 / 代替。
- 除法 / 是保留小数的,而整除 // 是直接取整的。
- 幂运算中:2**3,2为底数,3为指数,换算成乘法为:2 x 2 x 2。
# 加减乘除,在末尾Ctrl+D复制
print(1 + 1)
print(3 - 2)
print(4 * 5)
print(9 / 7)
print(9 // 7)
print(10 % 7)
print(2 ** 3)
# 使用变量进行四则运算
a = 1 + 1
print(a)
2. 变量自运算
- 所以自运算,就是让变量本身的值累计运算,比如累加、累减等;
- += 累加,a += 1,相当于a = a + 1;
- -= 累减,a -= 1,相当于a = a -1;
- 其它雷同,不再赘述,加减最为常用;Python不支持a++语法;
# 累加
a+=1
print(a)
3. 温度转换器
- 王二狗被夏天40度的高温热晕了,对于科学爱好者的他还是要计算一下华氏度是多少?
- 转换公式为:F = 9 ÷ 5 × C + 32
# 温度转换
c = 40
f = 9 / 5 * c + 32
print(f)
04. 有点个性输出.转义
1. print()不详解
给命令行输出一段内容,可以使用 print() 函数;
说明 | 语法 |
---|---|
函数名 | print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout) |
参数 | objects --表示输出的对象。输出多个对象时,需要用 , (逗号)分隔。 |
sep – 用来间隔多个对象。 | |
end – 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符。 | |
file – 要写入的文件对象。 | |
返回值 | 无 |
- 如果只是单纯在控制台输出一些内容,只需要传递一个参数即可:
# 简单的数值输出
print(123)
- 输出的内容可以多条数据,默认空格隔开显示,可以通过参数更改:
# 可以输出多个数据,默认英文逗号隔开
# 输出时,默认会以空格来隔开显示
print(123, 456, 789)
# 通过sep参数改变显示时间隔符号
# 注意,参数的值需要通过双引号包含,因为它不是数值而是字符串
print(123, 456, 789, sep="-")
- 默认情况下,print输出会自动换行:
# 默认情况下一个print输出后会自动换行
# 可以通过end参数改变这一特性
print(123, end="|||")
print(456)
2. 转义字符
字符 | 说明 |
---|---|
\\ | 反斜杠 |
\’ | 单引号 |
\" | 双引号 |
\n | 换行符 |
- 以上是比较常用的转义字符,更多的可以搜索查询,暂时我们也用不到;
# 转义字符输出
print("\'\"\n123")
# 只使用一个print输出以下图形
***
***
***
print("***\n ***\n ***")
05. 类型.格式
1. 数据类型
类型名 | 转换函数 | 说明及示例 |
---|---|---|
整型 | int() | 将合法的字符数值或浮点数转换成整数 |
字符串型 | str() | 将任意内容转换成字符串型 |
浮点型 | float() | 将合法的字符数值或整数转换成浮点数 |
布尔型 | bool() | 将任意内容转换成布尔型 |
为了判断一个变量或内容,可以采用**type()**函数;
说明 | 语法 |
---|---|
函数名 | type(object) |
参数 | object --表现需要获取类型的内容 |
返回值 | 返回一个<class ‘类型符号’> |
# 使用type()获取四种类型
print(type(10))
print(type(10.15))
print(type("王二狗"))
print(type(True))
print(type(False))
-
布尔类型只有两个值:True和False,注意首字母是大写的;
-
布尔类型一般用于条件表达式的判断,这在后续课程会使用;
-
类型转换,有一些条件:
- 转换成整数时,包含字母或其它字符都不能转成整数,只有纯数字才可以
- 浮点数同上;
- 布尔型只能转换成True和False;
# 类型转换
print(int("123"))
print(int(123.34))
print(float("123"))
print(bool(0))
print(bool(1))
print(type(str(123)))
- 将整型转换成字符串时,无法直观的检查出,所以需要用type()判断;
2. 字符串拼接
字符串的连接可以使用 +号 或者 逗号 ,具体如下:
# 字符串拼接,+号紧凑,逗号松散
print("我"+"和"+"你")
print("我" + "和" + "你")
print("我","和","你")
# 变量同理
a = "我"
b = "你"
print(a + b)
3. 输出格式
格式符 | 说明 |
---|---|
%d | 格式化整数,会直接取整 |
%f | 格式化浮点数,可以设置保留的小数点 |
%s | 字符串 |
- 格式化语法如下:
- “字符串文字%d %s %f”%(3, “嗯嗯”, 3.1415926)
# %符的格式化处理
name = "小明"
age = 3.33
weight = 1.23456
print("我的名字叫%s,今年%d岁了,身高%.2f"%(name, age, weight))
- %5.2f:可以理解为5个占位符,不足的用空格占用;
- %10s和%-10s:可以理解为占用10个位置,占用前面和占用后面的区别;
06. 输入
1. input()不详解
如果想让用户在键盘输入内容,可以使用 input() 函数;
说明 | 语法 |
---|---|
函数名 | input([prompt]) |
参数 | prompt --表示提示输入,[]表示它是可选状态。 |
返回值 | 返回提示内容 |
- 如果提示输入为空,则用户会黑灯瞎火的输入,除非本身知道;
# 在这里写上你的代码 :-)
# 无任何提示的输入,运行时控制台的光标闪烁
input()
# 可以将输入的内容,赋值给一个变量
# input()输入的内容,不管字面上是什么都是字符串类型
a = input()
print(a)
print(type(a))
- 如果加上提示内容,将很好的提示用户正确的输入;
# 有提示的输入
b = input("请输入一个数,1-9之间:")
print(b)
2. 输入转换运算
通过输入的两个数值进行运算,必须进行转换;
# 通过输入进行数值计算
c = int(input("请输入第一个数,1-9之间:"))
d = int(input("请输入第二个数,1-9之间:"))
print("%d+%d=%d"%(c, d, c+d))
- 最后,再做个经典的图形输出,输入指定图形元素(比如*)得到如下的图:
*
***
*****
# 输出三角图形,这里的*号是字符乘法,复制显示数量
e = input("请输入任意字符:")
print(" ",e)
print("",e*3)
print(e*5)
07. 是对还是错.布尔类型
1. 关系运算符
布尔类型的值,是专门用于各种表达式的判断,表达式的运算符如下:
名称 | 关系运算符 | 表达式返回值 |
---|---|---|
大于 | > | 成立范围True(6 > 5),否则为False(6 > 7) |
小于 | < | 成立返回True(6 < 8),否则为False(6 < 5) |
等于 | == | 成立返回True(6 == 6),否则为False(6 == 7) |
大于等于 | >= | 成立返回True(7 >= 6),否则为False(7 >= 8) |
小于等于 | <= | 成立返回True(6 <= 6),否则为False(6 <= 5) |
不等于 | != | 成立返回True(6 != 5),否则为False(6 != 6) |
# 表达式返回值
print(6 > 5)
print(6 > 7)
print(6 < 7)
print(6 < 5)
print(6 == 6)
print(6 == 7)
print(6 >= 7)
print(6 <= 7)
print(6 != 7)
2. 逻辑运算符
除了关系运算符之外,还有一种叫做逻辑运算符,具体如下:
名称 | 逻辑运算符 | 表达式返回值 |
---|---|---|
and | x and y | x和y同时为True时,返回True,否则返回False |
or | x or y | x和y只要其中之一为True,返回True,否则返回False |
not | not x | x为True时,结果为False,否则为True |
# 逻辑运算符
print(5 > 6 and 5 > 4)
print(7 > 6 and 5 > 4)
print(5 > 6 or 5 > 4)
print(5 > 6 or 5 < 4)
print(not 5 > 6)
print(not True)
08. 条件判断
1. if…单一条件
假设你一周七天中只有周一才能穿新衣服,那么就需要 if语句 中 单一条件判断:
- 单一条件判断的if语句格式如下:
if 条件判断:
条件满足时,执行
# 注意1:判断的数值需要转换为整数再判断
# 注意2:a == 1由于习惯或方式,可以加上括号(a == 1)
# 注意3:if条件判断内的语句,需要用Tab键缩进才能识别
a = int(input("请输入今天星期几,1-7之间:"))
if a == 1:
print("今天穿新衣")
2. if…else分支
单一if语句比较高冷,如果未满足条件,ta就不理你了;而else分支则可爱许多;
- else分支条件判断的if语句格式如下:
if 条件判断:
条件满足时,执行
else:
条件不满足时,执行
# 注意1:else后面有冒号,表示结束
# 注意2:else需要和if在同一垂直位对齐,否则报错
if a == 1:
print("今天穿新衣")
else:
print("今天无法穿新衣")
3. 日常穿衣表
夏天到了,汗渍很多,每日换洗:1,3,5穿A款,2,4,6穿B款,星期日放假不出门;
a = int(input("请输入今天星期几,1-6之间:"))
if a == 1 or a == 3 or a == 5:
print("穿A款")
else:
print("穿B款")
9. 我可以退休吗.多重.嵌套
1. elif…多重分支
年龄决定一个人当前的状态,8岁到80岁,你都在干什么?此时需要elif多重分支:
if 条件判断:
条件满足时,执行
elif 条件判断:
条件满足时,执行
...
# 8-25之间:求学阶段
# 26-60之间:工作阶段
# 大于60:退休阶段
a= int(input("请输入你的年龄:8-80之间:"))
if a >= 8 and a <=25:
print("我在上学!")
elif a >=26 and a<=60:
print("我在工作!")
elif a > 60:
print("我在退休!")
else:
print("这个年龄,尚未被统计!")
2. if…嵌套判断
难道大于60岁就真的可以退休了吗?真实情况显然不是这样的,我们还需要具体判断:
...
elif a > 60:
# 如果爷爷有大伯,二伯和我爸
# 且,我还有姐姐和弟弟
# 再且,大伯、二伯每家还有三个
# 为了存压岁钱,也无法退休呢!
b = int(input("请输入子孙的数量,1-9之间:"))
if b >= 7 and b <=9:
print("退休后打两份工")
elif b >= 4 and b <=6:
print("退休后打一份工")
elif b <= 3:
print("退休中!")
...
11. 存钱买switch.for循环
1. for…有限循环
王二狗考试得了双百,就得意的要求买台switch,还保证一周只玩三小时,但妈妈让他自己存;
for 变量 in 集合:
执行语句
2. rang()数值生成
想要实现一个月1-30号的自增循环,那需要使用**rang()**函数;
说明 | 语法 |
---|---|
函数名 | range(start[,end,step]) |
参数 | start:计数从start开始,默认是从0开始 |
end:计数到end结束,但是不包括end | |
step:每次自增的数值,默认1 | |
返回值 | 无 |
# 注意1:for...in...语法核心
# 注意2:i是变量,随便定义,每次获取自增后的值
# 注意3:range(1,30)实际上循环到1-29
# 注意4:range第三个参数,设置2体会下
for i in range(1,30+1):
print(i, end="|")
# 如果range只传入一个参数,默认从0开始到29
for i in range(30):
print(i, end="|")
- 此时,我们再回到上方的流程图,可能会略懂一二了;
- 但是,我们怎么才能知道循环体内的语句在重复执行呢?
- 这时,可以采用调试中的步进或步入来逐条执行语句;
3. 终于可以存钱了吗?
使用以上学到的循环语句,我们存一个月的钱,对自己狠一点,每天存的钱比前一天多一元;
# 存一个月的钱
t = 0
for i in range(1, 30+1):
t += i;
print("一个月存了%d元!"%(t))
12. 逃避可耻但有用.退出
1. break退出整体
过不下去啦,每天要存的金额越来越多,还是放弃吧,王二狗说道;此时,我们需要使用break终止循环;
for i in range(1, 30+1):
# 坚持到第3天,就要放弃
# 注意:如果将if放在print后面,将执行第3天
if i == 3:
break
print(i, end="|")
- break语句的作用:是退出整个循环;
2. continue退出当前
逃避的加锁禁锢着王二狗的内心,悟道自律才能获得自由;决定:偶数天存钱,奇数天放纵;理由:循序渐进,慢慢来;此时,我们就需要使用continue终止当前循环;
for i in range(1, 30+1):
# 先来简单的,假设第三天不存
# 注意:continue必须在执行语句前面执行,否则白搭
if i == 3:
continue
print(i, end="|")
- 计算偶数的方法:当天日期除以2的余数为0即为偶数,反之奇数;
for i in range(1, 30+1):
#判断为奇数就退出当前循环
#去自增执行下一次循环
if i % 2 == 1:
continue
print(i, end="|")
13. 蒙眼拉磨的驴.while循环
1. while常规循环
常规的 while循环 和 for循环 基本一致,只不过while更加灵活一点;
# 初始化变量1
i = 1
# 如果变量小于等于30,则执行循环体
while i <= 30:
print(i, end="|")
# 当执行完毕,自增1
i += 1
- while循环除了普通的循环,还有不少独有的做法,比如死循环;
2. while无限循环
王二狗家有一头拉磨的驴,ta的主人告诉ta,只要每天一直往前走,就会有精彩的未来;此时,我们就需要使用while中的无限循环,让其无穷无尽的执行下去;
# 只要判断表达式永远为True,则不停的执行
while True:
- 请编写一只怀揣梦想、坚持不懈、勇往直前、誓不罢休的驴;
while True:
print("驴:请问,到达路的尽头了吗?^.^")
a = input("请告诉驴,到达与否,回车或输入yes:")
if a != "yes":
print("旁白:还没到,请继续向前!")
print("驴:加油!加油!努力!奋斗!")
else:
print("旁白:已经到了!")
print("驴:真好,我累了,要睡了!❤")
break
print("-----------------------")
14. 我家住在302.列表入门
1. 九宫格解析
列表其实就是一种可以同时存放多个数据的变量,就好比如下图存放了9条数据,但只有一个变量;
101 | 102 | 103 |
201 | 202 | 203 |
301 | 302 | 303 |
- 分析上图的九宫格,王二狗家住在第三行第二列的位置上;整体看上去像个容器,例如超市存包的柜子;
- 我们如何设置它,获取它,使用它?那就必须是用今天的列表变量;列表声明方式为:
- 列表变量 = [ 数据1, 数据2, 数据3… ]
# 注意1,array是列表变量名,是随便定义的
# 注意2:列表的值外围是一个中括号,里面的每个数据可以称为元素
# 注意3:每个元素,用逗号隔开,元素太多,可以换行
# 注意4:元素遵循数据类型,如果是字符串元素,要加引号
# 注意5:其它语言一般称为数组,如果我不小心说数组啥啥啥的,等同
array = [101, 102, 103,
201, 202, 203,
301, 302, 303]
# 输出列表,带上中括号原封不动的输出
print(array)
# 看看什么类型,<class 'list'>列表类型
print(type(array))
2. 理解下标
什么是下标?即:列表中元素对应的位置编号,或称为:索引,默认从0开始计算;
0 | 1 | 2 |
3 | 4 | 5 |
6 | 7 | 8 |
# 获取王二狗的家
print(array[7])
-
除了直接通过下标获取指定位置的元素值,还有一些其它语法:
-
为了避免混淆,我不把下标0说成第1个位置(我会说下标为0的元素);
-
array[0]:获取下标为0的位置的元素,这里获取的是101;
-
array[2:4]:获取下标为2到3的元素,这里获取的是[103, 201],返回一个新list;
-
array[2:]:获取下标为2到end的元素,这里获取的是[103…303],返回一个新list;
-
array[:2]:获取下标为start到1的元素,这里获取的是[101,102],返回一个新list;
-
array[-2]:获取倒数第2个元素,这里获取的是[302];
-
array[-5:-2]:获取倒数第5到倒数第3的元素,这里获取的是:[202, 203, 301],返回一个新list;
-
print(array[0])
print(array[2:4])
print(array[2:])
print(array[:2])
print(array[-2])
print(array[-5:-2])
15. 点出万物.列表操作
1. 点语法启蒙
列表是一种容器类的结构,势必会给予它较为丰富的操作,比如点语法的操作方法:
列表变量.操作方法()
- .操作方法()可以理解为列表变量下的特有函数;
方法名 | 说明 |
---|---|
.append(o) | 在列表末尾添加o元素 |
.insert(i, o) | 在指定位置插入o元素 |
.remove(o) | 移除匹配到o元素的第一个 |
.pop(i) | 取出列表中最后一个元素,或指定元素;然后返回该元素的值 |
.extend(s) | 在列表尾部合并另一个列表 |
2. 增删改查
通过自身语法以及点语法,我们可以对列表进行一系列的 增删改查 操作:
- 先创建一个空列表,以便于我们操作:
# 创建一个空的列表
array = []
print(type(array))
- .append()方法添加元素,会在列表尾部添加一条
# 使用.append()方法添加元素
array.append(101)
array.append(102)
array.append(103)
print(array)
- .insert()方法在某一处插入元素,用下标来指定位置
# 使用.insert()在指定下标位置插入元素
array.insert(0, 109)
print(array)
- .remove()方法删除元素,会找到第一个匹配值的元素删除
# 使用.remove()方法删除元素
# 注意:参数需填写元素的值
array.remove(102)
print(array)
- .pop()方法取出元素,使用下标定位,再返回这个元素值
# 使用.pop()方法删除元素
# 注意1:这个删除严格意义上是取出返回的意思
# 注意2:参数需填写元素的下标
print(array.pop(1))
print(array)
- 列表修改的方法,直接通过下标定位赋值
# 使用[]方式修改元素,传递下标位置即可
array[0] = 1001
print(array)
- .extend()方法参数是要合并的列表,添加到尾部
# 使用.extend()方法合并列表
list = [1002, 1003]
array.extend(list)
print(array)
16. 超好用的列表工具箱
1. 工具方法
包括获取列表元素的数量、获取匹配的下标位置、排序、复制、清空等;
方法名 | 说明 |
---|---|
.count(o) | 统计元素o在列表中出现的次数 |
.index(o) | 获取元素o在列表中第一次出现的位置 |
.reverse() | 反转列表 |
.sort(reverse=True) | 排序,默认从小到大排序,设置reverse参数为True则反之 |
.copy() | 复制列表 |
.clear() | 清空列表 |
- 依旧先创建一个九宫格列表
# 根据不同演示的方法会做出修改
array = [101, 102, 103,
201, 202, 203,
301, 302, 303, 102, 102]
- .count()方法获取指定元素出现的次数
# .count()方法
print(array.count(102))
- .index()方法获取指定元素第一次出现的位置
# .index()方法
print(array.index(102))
- .reverse()方法可以让原列表进行翻转
# .reverse()方法,反转原列表
# 注意:这个方法本身不返回反转,而是让原列表反转
array.reverse()
print(array)
- .sort()方法可以让原列表进行排序,参数可以设置大小排序
# .sort()方法,默认从小到大排序原列表
# 参数reverse=True|False,从大到小和反之
array.sort()
print(array)
array.sort(reverse=True)
print(array)
- .copy()方法,是拷贝一份独立的列表赋值给新变量
- .clear()方法,清空列表
# .copy()方法,将列表复制出来交给新变量
# 注意:赋值和拷贝的区别,本课不深究
list = array.copy()
#list = array
array.pop()
print(array)
print(list)
# .clear()方法,清空
list.clear()
2. 工具函数
这里推荐三个极其常用的函数,来快速获取想要的对应数据:min()、max()、len();
# len()函数,获取长度
print(len(array))
# min()函数,获取最小值
print(min(array))
# max()函数,获取最大值
print(max(array))
3. del语句和遍历
还有一个删除列表的快捷语句 del 和 循环列表 数据的遍历方法;
# 删除某一个元素或多个元素
del array[0]
del array[2:4]
# 删除整个列表
# 注意:这里不是清空,是直接把array变量给抹除了
del array
# 遍历列表,两种方法,1.没必要 2.不错
for i in range(len(array)):
print(array[i], end="|")
print()
for i in array:
print(i, end="|")
17. 我…只可远观.元祖
1. 只读的列表
元祖相对于列表,有两个最主要的特性:1. 数据集用圆括号包含;2. 数据只读,无法修改删除;
# 注意1:创建时()包含,但输出还用[]
# 注意2:无法修改删除,array[0] = 1001 将报错
# 注意3:type()后得知元祖是tuple[tʌpl]类型
# 注意4:如果创建的元祖只有一条元素,需要补上逗号
array = (101, 102, 203)
print(array)
print(array[0])
print(type(array))
2. 元祖遍历
遍历和列表一致;
# 元祖遍历
for i in array:
print(i, end="|")
18. 花括号有话要说.字典
1. 一一配对的概念
字典的核心就是:配对!即:键值对(key:value)!就是将列表中的下标索引进行自定义设计;
# 字典创建的语法,通过 花括号 以及 key:value 的方式构成:
字典变量 = {键:值, 键:值, ...}
- 创建空的字典,有两种方式:
# 创建空字典,有两种方式1. {} 2. dict()
#array = {}
array = dict()
print(type(array))
- 创建带数据的字典:
# 创建字典,键值配对
# 注意1:冒号左边称为:键(key),右边称为:值(value)
# 注意2:依然遵循数据类型规则,key或value,数字不加引号,字符加
# 注意3:通过key找value,依然用中括号,但字符串不能省略引号
array = {
"A1":"张三",
"A2":"李四",
"A3":"王二狗",
"A4":"马六"
}
print(array)
print(array["A3"])
2. 字典的操作
和列表一样,字典也需要进行增删查改的操作;
# 字典新增
array["A5"] = "王五"
# 字典删除,和列表一样
#del array["A4"]
array.pop("A4")
# 字典修改
array["A1"] = "张四"
-
和列表一样,字典也具有**.copy()和.clear()方法以及min()**、**max()和len()**等函数,这里不再赘述;
-
字典也有自己独有的方法,方便更好的操作:
方法名 | 说明 |
---|---|
.get(key, default) | 返回指定key的值,如果不存在则返回default默认值 |
.keys() | 返回字典中所有的key组成的列表,可再通过遍历输出 |
.values() | 同上 |
.items() | 返回字典中所有的(key,value)组成的元祖,再通过遍历输出 |
# 获取指定key的value,如果不在,返回指定值
print(array.get("A3"))
print(array.get("A9", "不存在"))
# 通过.keys()返回所有key
for i in array.keys():
print(i, end="|")
print()
# 通过.values()返回所有values
for i in array.values():
print(i, end="|")
print()
# 通过.items()返回所有(key,value)
for i in array.items():
print(i, end="|")
19. 学籍卡系统.二维查
1. 二维的概念
什么是一维?就是普通列表,一行数据;什么是二维?就是多行数据,表格式数据;
- 我们首先要创建一个空的字典,用来存储每个学员的学籍卡;
# 初始化区
s = {}
- 但是,为了测试查询方便,先填充三条学员数据,具体形式如下表格:
编号 | 姓名 | 性别 | 年龄 |
---|---|---|---|
A1 | 张三 | 男 | 12 |
A2 | 李四 | 女 | 11 |
A3 | 王二狗 | 男 | 12 |
# s是字典
# 注意1:整个学籍卡用字典来表示,这样直接可以通过key快速查找
# 注意2:但是value不是普通的数据,它是一个列表,是一串数据
# 注意3:对于字典里的value,本身可以通过s["A3"]访问,但得到是list类型
# 注意4:对于list列表类型,再通过[]号获取:s["A3"][0]得到"王二狗"
# 注意5:两个中括号,可以理解为嵌套list,或list里的list,这就是二维
s = {
"A1":["张三", "男", 12],
"A2":["李四", "女", 11],
"A3":["王二狗", "男", 12],
}
2. while Ture
对于一款小软件,只有在用户主动关闭才能退出,我们通过死循环来实现;
# 死循环
while True:
# 选项开启
n = int(input("请输入要操作的选项:\n1.查看学籍 2.添加学籍 3.修改学籍 4.删除学籍\n请选择,1-4之间:"))
# 判断选项
if n == 1:
print("show")
elif n == 2:
print("add")
elif n == 3:
print("update")
elif n == 4:
print("delete")
else:
print("error")
- 用二维查询的方法建立表格,实现数据的展现
# 标头
print()
print("%-3s %-7s %-5s %-5s"%("编号", "姓名", "性别", "年龄"))
print("------------------------")
# 遍历学籍卡
for i in s.items():
print("%-4s %-8s %-5s %-5s"%(i[0], i[1][0], i[1][1], i[1][2]))
print("------------------------")
print()
20. 学籍卡系统.增删改
1. 增加数据
通过input()录入来写入数据到字典中,key是编号,而value是列表;
# 信息录入
a = input("请输入编号:")
b = input("请输入姓名:")
c = input("请输入性别:")
d = input("请输入年龄:")
# 写入s
s[a] = [b,c,d]
#空一行
print()
2. 修改数据
查询数据,通过input()获取编号后,录入修改的内容,再更新;
# 查询并修改
f = input("请输入编号:")
if s.get(f, "no") != "no":
b = input("请输入姓名:")
c = input("请输入性别:")
d = input("请输入年龄:")
s[f] = [b, c, d]
else:
print("编号有误!")
#空一行
print()
3. 删除数据
查询数据,通过input()获取编号后,直接del即可;
# 查询并删除
f = input("请输入编号:")
if s.get(f, "no") != "no":
del s[f]
else:
print("编号有误!")
#空一行
print()
21. 重新回顾字符串类型
1. 常规功能合集
字符串本身有一些功能,有些之前运用过,这里总结如下:
# 功能一:判断字符串类型
print(type("Hello"))
print(str(123)) # 转换
# 功能二:连接字符段片段
a = "hello"
b = "python!"
print(a + b) # 没有空格
print(a, b) # 包含一个空格
# 功能三:重复字符
c = "^.^ "
print(c * 5)
2. 类似列表的特性
如果把字符串每个字符当成列表的元素,将会有很多处理方案:
# 将字符串理解为列表
# 注意:但是列表中方法和工具不一定都能用
d = "abcdefghijklmn"
# 输入b,那么下标位置为1
print(d)
print(d[1])
print(d[2:8])
# 第三个冒号是step步长
print(d[2:8:2])
# 可以用工具函数
print(len(d))
# 字符串也可以像列表一样遍历
for i in d:
print(i, end="|")
3. 格式化数据
之前已经学习过%d、%f、%s等格式化输出的方式,再看一些变种:
# 格式化输出
#t = "hello"
#s = t + "python"
t = "hello%s"
s = t%"python"
print(s)
x = "我叫%s,今年%d岁,身高%.2f米"
y = x%("王二狗",10,1.45)
print(y)
22. 超好用的字符串工具箱
1. 判断方法
字符串有一些用于判断数据的工具函数:成立返回True,失败返回False
方法名 | 返回值说明 |
---|---|
.isnumeric()、.isdigit() | 判断字符串是否只包含了数字 |
.isalpha() | 判断字符串是否只包含了字母 |
# 判断是否只包含数字
a = "123"
b = "123abc"
print(a.isdigit())
print(b.isdigit())
# 判断是否只包含字母
c = "abc"
d = "abc123"
print(c.isalpha())
print(d.isalpha())
2. 转换方法
字符串有一些用于转换数据的工具函数:直接返回转换后的数据
方法名 | 返回值说明 |
---|---|
.lower() | 将英文的字符串全部转换为小写 |
.upper() | 同上,大写 |
.format() | 格式化 |
# 大小写转换,格式化
print("abCdeFghiJklmn".upper())
print("abCdeFghiJklmn".lower())
print("123{}45{}6".format("x","y"))
print("123{1}45{0}6".format("x","y"))
3. 查询处理方法
字符串有一些用于查询处理数据的工具函数:返回新的字符串
方法名 | 返回值说明 |
---|---|
.find(s[, start, end]) | 判断是否包含子字符串,返回下标索引,否则返回-1 |
start:开始下标索引位置,默认0,end:结束下标索引位置,默认为字符串长度 | |
.replace(s1, s2[,max]) | 将字符串中的s1替换成s2后生成新的字符串 |
s1:将要被替换的字符串 s2:新字符串 max:替换次数 | |
.split(s, n) | 将字符串切割后形成一个字符串列表 |
s:分隔符 n:切割次数 默认值:-1,即切割所有 |
# 查询、替换、切割
print("abCdeFghiJklmn".find("d"))
print("abCdeFghiJklmn".find("d", 6,10))
print("1abCd1eFgh1iJk1lmn".replace("1", "2"))
print("1abCd1eFgh1iJk1lmn".replace("1", "2", 2))
print("1abCd1eFgh1iJk1lmn".split("1"))
print("1abCd1eFgh1iJk1lmn".split("1", 2))
23. 我点一份随便.随机库
1. 引入库概念
Python有大量的功能需要借助丰富的库来实现,随机库 random 是其中一种:
# 引入随机库
import random
# 随机1-100之间
print(random.randint(1,100))
- 一般来说,库的原名太长,我们可以设置一个别名方便调用
# 设置库的别名
import random as r
print(r.randint(1, 100))
2. 常用的随机方法
除了上面的 .randint() 整数随机,还有其它的一系列随机数:
方法名 | 返回值说明 |
---|---|
.randint(s, e) | 返回s~e之间的一个随机整数 |
.uniform(s, e) | 返回s~e之间的一个随机小数 |
.randrange(s, e, p) | 同第一条,p是步长 |
.choice(s) | 从元祖中随机获取一个元素 |
.shuffle(s) | 打乱原列表 |
.sample(s, n) | 随机从s列表中取出n个元素组成新列表 |
# 随机小数
print(r.uniform(1, 10))
# 随机规律整数
# 随机奇数,偶数
print(r.randrange(1, 10, 2))
print(r.randrange(2, 11, 2))
# 从元祖数据中随机产生一个
print(r.choice(("ScratchJR", "Scratch", "Python", "C++")))
# 打乱原列表
array = ["a", "b", "c", "d", "e"]
r.shuffle(array)
print(array)
# 随机取出元素组成新列表
list = [1, 2, 3, 4, 5]
print(r.sample(list, 3))
24. 小海龟起航.turtle库
1. turtle库入门
turtle,即:海龟;和随机库一样,是Python中一种 绘图工具 库,也叫 海龟库:
- 先用最少的代码,画上一笔,注意会有图形界面:
# 引入海龟库
import turtle as t
# 画一笔,向前(默认右)画100步(单位:像素)
t.forward(100)
方法名 | 返回值说明 |
---|---|
.forward(d) | 面朝当前方向前进d个距离,d单位是像素 |
.back(d) | 同上,后退 |
- 绘图结束时,需要使用.done()方法来暂停绘制:
# 暂停画笔绘制,不会关闭窗口,等待用户交互(比如关闭窗口)
# 注意1:在某些其它Python开发工具,没有.done()会自动关闭窗口
# 注意2:而.done()方法放在绘图代码结尾,保证窗口悬停
# 注意3:既然.done()是暂停的意思,后面再绘图便无效
# 注意4:本开发工具Mu Editor不会自动关闭,学习时不必每次调用
t.done()
2. 角度的概念
在绘制图形时,有时需要转向,那么就需要调整前进箭头的指向(角度)即可
方法名 | 方法说明 |
---|---|
.left(a) | 设置当前角度左转a度,a的取值0-360 |
.right(d) | 同上,右转 |
# 拐个弯,向左拐90度
t.left(90)
# 再向前100步
t.forward(100)
# 拐个弯,向右拐90度
t.right(90)
# 再向前100步,或后退
#t.forward(100)
t.back(100)
3. 设置箭头造型
为什么叫小海龟呢?因为箭头是可以设置为小海龟的,当然,默认是箭头
方法名 | 方法说明 |
---|---|
.shape() | 设置箭头的造型:arrow、turtle、circle、square、triangle、classic |
.hideturtle() | 设置绘图结束后,隐藏箭头 |
.showturtle() | 同上,相反 |
# 设置箭头造型
t.shape("turtle")
# 隐藏海龟
t.hideturtle()
25. 正方形.三角形.多边形.圆
1. 正方形.三角形
通过计算总度数(360)除以要绘制的边数,就可以得到正方形和三角形的绘制角度
- 正方形四条边,那么角度为:360 / 4 = 90°
- 三角形三条边,那么角度为:360 / 3 = 120°
# 绘制正方形
# 注意1:forward可以简写fd,back=>bk
# 注意2:right可以简写rt,left=>lt
# 注意3:推荐初学者用完全单词,不然都不知道啥意思
for i in range(4):
t.forward(200)
t.right(90)
# 绘制三角形
for i in range(3):
t.forward(200)
t.left(120)
2. 多边形绘制
我们需要通过计算多边形的角度来获取要拐弯的角度,具体如下:
- turtle库提供了类似input()函数的输入方法:
方法名 | 方法说明 |
---|---|
.numinput(t, p) | 弹窗输入框,t为标题文本,p为描述信息 |
# 获取多边形的边
n = t.numinput("OK", "请输入多边形的边数:")
# 绘制多边形
for i in range(int(n)):
t.forward(200)
t.right(360 / n)
3. 圆
圆的绘制,有自己单独的方法:.circle()
方法名 | 方法说明 |
---|---|
.circle(r[, extent=None, steps=None]) | 绘制一个圆,r是半径,可负数 |
e可选,弧形的角度 | |
s可选,多边形边数 |
# 绘制圆
t.circle(150)
t.circle(-150)
# 半圆
t.circle(150, 180)
# 多边形
t.circle(150, 180, 7)
t.circle(150, 360, 7)
t.circle(150, steps=7)
26. 品味穿搭.画笔设置
1. 画笔设置
海龟库绘制时的线条是可以设置的:包括颜色、粗细以及绘制速度等
方法名 | 方法说明 |
---|---|
.speed(s) | 设置绘图的速度:0或大于10表示瞬间绘制,1~10之间,1最慢,10最快 |
.color(p[,f]) | 设置所有颜色,包括笔头和绘制的线条,以及填充 |
.pencolor© | 设置画笔颜色,绘制的线条以及笔头的轮廓部分 |
.pensize(s) | 设置画笔粗细,填入数字,最小值1 |
# 设置为海龟
t.shape("turtle")
# 设置颜色,只有一个参数,则设置所有,如有第二参数,为填充
t.color("blue", "green")
# 设置画笔颜色,箭头只会轮廓被覆盖
t.pencolor("red")
# 设置画笔粗细
t.pensize(5)
# 设置绘制速度
t.speed(0)
- 设置颜色的方式推荐两种:英文单词和RGB代码
- 英文单词比如:black、red、blue等;
- RGB代码比如:(0, 0, 0)、(255, 255,255),范围0~255之间;
红 | 黑 | 白 | 黄 |
---|---|---|---|
red | black | white | yellow |
绿 | 粉 | 橙 | 紫 |
green | pink | orange | purple |
- 图片来源于:https://blog.csdn.net/piglite/article/details/105407429
2. 填充设置
如何在绘制好的封闭区域进行填充呢?比如一个圆
方法名 | 方法说明 |
---|---|
.fillcolor© | 设置填充的颜色 |
.begin_fill() | 在需要填充的封闭区域前设置 |
.end_fill() | 在需要填充的封闭区域后设置 |
# 绘制一个圆
# 默认会填充会依附于t.color()
# 单独设置使用t.fillcolor()
t.fillcolor("orange")
t.begin_fill() # 开始填充
t.circle(100)
t.end_fill() # 结束填充
3. 窗体设置
绘图部分都设置完毕了,那么运行的窗体、背景啥的能修改吗?
方法名 | 方法说明 |
---|---|
.setup(w, h) | 设置窗体的大小 |
.title(s) | 设置窗体的标题 |
.Screen().bgcolor© | 设置屏幕的背景颜色 |
.bgcolor© | 同上 |
# 设置窗体大小
t.setup(800, 600)
# 设置窗体标题
t.title("品味穿搭")
# 获取屏幕设置变量
s = t.Screen()
# 背景颜色
s.bgcolor("pink")
27. 同心圆.坐标与定位
1. 坐标的概念
默认情况下,海龟绘图在屏幕中间开始。我们可以设置对应坐标让其在指定位置绘制
方法名 | 说明 |
---|---|
.goto(x, y) | 设置海龟箭头的位置,默认0, 0 |
.setx(n) | 设置海龟箭头的x位置 |
.sety(n) | 设置海龟箭头的y位置 |
.home() | 回到初始位置,并设置初始方向 |
# 将坐标定位至x:0,y:0
t.goto(0,0)
t.circle(200)
# 将坐标y轴上移100位置
t.goto(0, 100) # 或t.sety(100)
t.circle(100)
- 虽然,我们绘制出了同心圆,但暴露了两个问题:
- 问题一:同心圆没有按照中心点绘制;
- 问题二:当绘制第二个圆时,笔头划过路过的区域,并没有抬笔;
# 按照中心点绘制圆
n = t.numinput("同心圆", "请输入外围圆的半径:")
t.goto(0, 0 - n)
t.circle(n)
t.goto(0, 0 - n / 2)
t.circle(n / 2)
2. 抬笔与落笔
每次当笔头要位移绘制时,我们需要将笔头抬起,然后再绘制处再落笔即可
方法名 | 说明 |
---|---|
.penup() | 设置画笔抬起,此时无法绘制内容,简写为:.up() |
.pendown() | 设置画笔落下,此时可以绘制内容,简写为:.down() |
# 起始先抬笔
t.penup()
# 绘制时落笔
t.pendown()
28. 到此一游.点和文字
1. 坐标设计
想设计一个x轴和y轴的坐标图形,先利用已有的知识绘制出来
# x轴
t.penup()
t.goto(-50,0)
t.pendown()
t.forward(250)
# y轴
t.penup()
t.goto(0, -50)
t.left(90)
t.pendown()
t.forward(250)
- 上面的代码有两个问题:
- 我们可以通过设置箭头的方向,而不是left拐弯;
- 箭头只能保持一个,需要使用驻留的功能保持;
方法名 | 说明 |
---|---|
.seth(a) | 设置海龟箭头方向, |
右(0)、上(90)、左(180)、下(-90, 270) | |
.stamp() | 将箭头标记驻留在绘制结尾 |
# y轴
t.penup()
t.goto(0, -50)
t.seth(90)
t.pendown()
t.forward(250)
t.stamp()
2. 点和文字
在坐标(0, 0)位置设置一个中心点,并在点写上文字
方法名 | 说明 |
---|---|
.dot(s[, c]) | 绘制一个点,s为大小,c可选颜色 |
.write(s[, move, align, font]) | 写入文字,s是文字,move是true或false,设置画笔初始位置 |
align是方向:left(左)、right(右)、center(中) | |
font是字体:“宋体”, 20, “bold”,字体是本机的字体,20是字体大小 | |
bold是加粗,另外选项:noraml(正常)、italic(倾斜) |
# x轴
t.penup()
t.goto(-50,0)
t.pendown()
t.forward(250)
t.stamp()
t.write("x", align="left", font=("宋体", 12, "bold"))
# y轴
t.penup()
t.goto(0, -50)
t.seth(90)
t.pendown()
t.forward(250)
t.stamp()
t.write("y", align="right", font=("宋体", 12, "bold"))
# 中心点
t.goto(0, 0)
t.dot(10)
t.write("(0,0)", align="left", font=("宋体", 12, "bold"))
# 隐藏箭头
t.hideturtle()
29. 不想再次重复.函数
1. 函数的概念
我们这里所说的函数有两个意思:系统内置函数和自定义函数
- 具体区别如下:
- 系统内置函数:之前学习的print()、input()等 固定英文+括号 的语法格式,并有独有的效果;
- 自定义函数:今天即将要学习的自己创建的 自定义英文+括号 的语法格式,效果是自己的创意和算法;
- 那么,先看一个需求:我要在屏幕左上、左下、右上、右下绘制四个圆:
# 引入海龟库
import turtle as t
# 加速
t.speed(10)
# 抬笔
t.penup()
# 去左上角
t.goto(-150, 150)
# 落笔
t.pendown()
# 画圆
t.circle(100)
# 抬笔
t.penup()
# 去右上角
t.goto(150, 150)
# 落笔
t.pendown()
# 画圆
t.circle(100)
# 抬笔
t.penup()
# 去左下角
t.goto(-150, -150)
# 落笔
t.pendown()
# 画圆
t.circle(100)
# 抬笔
t.penup()
# 去右下角
t.goto(150, -150)
# 落笔
t.pendown()
# 画圆
t.circle(100)
- 从上面的例子中发现,非常简单的圆,只不过要重复四次,就花费了大量的代码片段,带来了很多问题:
- 代码重复度高,可读性差,查询修改代码会错位;
- 后期维护困难,如需升级,重复的地方都要修改;
2. 无参数的函数
函数的括号部分是用于传递参数的,如果留空那就是无参数的函数
- 语法格式:
- 创建函数:def 自定义函数名():
- 调用函数:自定义函数名()
# 创建一个函数,关键字def开头
# 注意1:和if语句一样,创建函数结尾加上冒号
# 注意2:和if语句一样,在函数体内必须有Tab键间隔
def hello():
print("我是函数!")
# 调用多次函数
hello()
hello()
hello()
3. 有参数的函数
函数的括号部分是用于传递参数的,如果留空那就是无参数的函数
- 语法格式:
- 创建函数:def 自定义函数名([参数1, 参数2, 参数3…]):
- 调用函数:自定义函数名()
# 函数的参数,本质上就是变量
def info(name, age):
print("我的名字叫:%s, 年龄:%d"%(name, age))
# 调用传递不同参数
info("张三", 12)
info("李四", 11)
info("王二狗", 12)
- 此时,我们再回到之前重复绘制圆的问题,用函数去解决它:
# 函数,解决了后期升级和可读性问题
def dc(x, y):
# 抬笔
t.penup()
# 去左上角
t.goto(x, y)
# 落笔
t.pendown()
# 画圆
t.circle(100)
# 左上
dc(-150, 150)
# 右上
dc(150, 150)
# 左下
dc(-150, -150)
# 右下
dc(150, -150)
30. 来了都收.传参技巧
1. 参数传递
除了无参和固定参数外,还有带默认值的参数以及不确定个数的参数
# 带默认值的参数
# 注意1:不带默认值的参数是必须传递的,否则报错
# 注意2:带默认值的,如果传递了则覆盖默认值,反之则启用默认值
def info(id, name, gender="未知"):
print(id, name, gender)
# 调用
#info(1) # 报错
# 启用默认值
info(1, "王二狗")
# 覆盖默认值
info(1, "王二狗", "男")
2. 不确定参数
什么叫做不确定参数?传递参数的个数不确定,有多有少,从而设计的传参方式
# 不固定的传参
# 需求说明:给同时进入大厅的客户发表问候
# 例如:1.张三,欢迎光临!2. 张三,李四,王二狗,欢迎光临!
# 注意1:传递的参数名和普通参数一样,自定义,args单词为多组参数的意思
# 注意2:参数左边有一个 * 号,表示我是不固定个数的传参
# 注意3:不管传递了多少个参数,都将形成一个 turple(元祖)类型,可遍历
def hello(*args):
for i in args:
print(i, end=",")
print("欢迎光临!")
hello()
hello("张三")
hello("张三", "李四", "王二狗")
- 如果想要在不固定参数的使用上,再添加一个固定参数,那需要在左边传递
def hello(info, *args):
for i in args:
print(i, end=",")
print(info)
hello("恭喜发财", "张三", "李四", "王二狗")
- 在不固定传参中,还有一种**args的传参方式,原理和上面一样,只是采用了key:value模式
# **args模式,特别适合key:value形式的数据传递
# 注意1:这个参数会返回一个 dict 字典数据
# 注意2:调用的时候,通过赋值传递即可
def student(**args):
#print(type(args))
for i in args.items():
print(i)
student(id="A1", name="张三", age=11)
student(id="A2", name="李四", age=10)
student(id="A3", name="王二狗", age=12)
31. 反馈不反馈.返回值
1. 无返回值函数
之前我们自定义的函数都是无返回值的函数,即:调用函数后,直接执行所有代码
# 无返回值函数
# 注意1:无返回值函数权限太高,自行执行需求,包括格式方法等
# 注意2:调用方只能调用和传参,没有任何其它权限
def hello(a, b):
print("%dx%d=%d"%(a,b,a*b))
hello(10,20)
hello(30,50)
- 我们希望函数能给调用者更多的权限,让调用者控制更多的需求和流程,可采用返回值模式
2. 有返回值函数
通过函数计算得到的结果后,不用去处理它的流程和显示格式,返回给调用方处理
# 有返回值函数
# 注意1:通过return关键字将结果返回出来
# 注意2:由于函数是返回结果,直接调用是无任何输出的
# 注意3:可以把有返回值的函数当前变量处理,输出或判断等
def info(a, b):
return a * b
print(info(10, 20))
print("%dx%d=%d"%(10,20,info(10,20)))
# 对函数返回值进行判断
if info(10,20) > 150:
print("结果正确!")
32. 相邻不相识.全局与局部
1. 局部变量
有没有考虑过一个问题:函数体内声明的变量会不会被外部干涉?
# 这里的num 叫全局变量
num = 100
# 在函数体内声明变量
def hello(x,y):
# 这里的num 叫局部变量
num = 10
return x + y + num
# 在这里的返回值中,参与运算的是局部变量num
print(hello(10, 20))
# 注意:这里的num一定是全局 num,局部num出了函数体就看不见了
print(num)
- 归纳以下几点:
- 局部变量只能在自己的函数体内参与操作,出了函数体就不认识了;
- 全局变量就是在函数体外部声明的变量,它无法改变函数体声明的同名变量;
- 局部变量比全局变量的优先度高,它会覆盖全局同名变量在函数体内的运行;
- 如果函数体内不存在同名的局部变量声明,那么函数体内的变量将由全局变量接管;
2. 全局变量
全局变量除了在函数体外直接定义,也可以在函数体内声明
# 全局变量解析
def info():
# 在函数体内创建一个全局变量
global x
x = 10
y = 20
# 在未调用时,x无法访问
#print(x)
# 在调用时,x可以访问,y是局部变量无法访问
info()
print(x)
#print(y)
-
归纳以下几点:
- 全局函数在函数体需要定义为:global x,赋值需要单独进行,不可直接赋值;
- global 关键字声明,必须在函数体的第一行;
- 函数体内的全局变量,需要调用过函数才能实现声明可见;
-
利用全局变量的特性,实现一个调用关系的小例子:
# 调用关系
def your():
print(name, age)
def my():
global name
name = "王二狗"
age = 12
# 外部初始化全局变量name和age
name = "张三"
age = 11
# 首先,调用your(),直接被全局变量接管
your()
# 其次,调用my(),会激活全局变量name和局部变量age
# 此时,name被新的全局变量覆盖,而age只是局部,无法接管另一个函数的同名变量
my()
your()
33. 模块化处理.函数分组
1. 函数分组
函数太多怎么办?将函数独立里成文件用库的方式import导入,实现模块化
# 加法函数,addition.py
def sum(x, y):
return x + y
# 模块其实就是引入库,比如import turtle
# 本节课主要是学会如何自己创建可以import的库
# 将函数存放到单独文件中,通过import调用
# import 导入同文件夹下的.py文件名
import addition as add
print(add.sum(10, 20))
2. from…import
引入模块库时,可以使用from…import可将指定方法导入模块中
# from...import只是将库中某一个或几个方法引入近来
# 多个方法,用逗号个开 circle,fd,bk
from turtle import circle
# 如果想将所有的turtle引入近来,直接import *
from turtle import *
print(add.sum(10, 20))
# 无法执行
#turtle.forward(100)
# 直接使用方法,当成函数语法使用
circle(100)
fd(100)
34. 反复调用.递归函数
1. 简单递归
什么是递归,即:重复的执行自身函数,从而解决某些问题
-
需求分析:阶乘问题
- 输入一个数n:比如5,求出1-n之间的阶乘:1x2x3x4x5
- 具体阶乘公式为:F(n) = n × F(n-1)
-
思路分析:从后向前乘
- 假设n为5,利用公式:n × F(n-1)
- 第一轮:5 x (5-1), 20
- 第二轮:20 x (4 - 1) , 60
- 第三轮:60 x (3-1), 120
- 第四轮:120 x (2-1),120
- 第五轮:判断1退出
# 阶乘递归函数
def F(n):
# 当n为1时,就需要退出递归不再调用
if n <= 1:
# return 除了返回还有类似break退出的作用
return 1
#print(123)
else:
# 5 * F(5-1)
# 5 * 4 * F(4-1)
# 5 * 4 * 3 * F(2-1)
# 5 * 4 * 3 * 2 * 1
return n * F(n - 1)
print(fn(5))
2. 斐波那契数列
提升一点难度,尝试用递归来算出第20位的经典数列:斐波那契数列
- 数列公式分析:
- F(n)=F(n-1)+F(n-2)
- 1、1、2、3、5、8、13、21…
- 从第三项开始,每一项都是前两项的和
# 斐波那契数列
def F(n):
# 前两项固定为1
if n == 1 or n == 2:
return 1
# 套用递归
# 如果n=3,F(2) + F(1) = 2
# 如果n=5, F(4) + F(3) = 3 + 2 = 5
# 如果n=8,F(7) + F(6) = 5 + 3 + 5 + 5 + 2 + 1 = 21
return F(n-1) + F(n-2)
print(F(20))
35. 数学函数及Math库
1. 数学函数
头疼的数学计算,提供了不少快捷的函数方便我们直接使用
函数名 | 返回值说明 |
---|---|
abs(x) | 返回x的绝对值 |
round(x[, n]) | 返回x四舍五入的值,n可选,保留小数的位数 |
# 绝对值,不管正负,都取正
print(abs(-5))
print(abs(5))
# 四舍五入,遇5进位
print(round(3.55))
print(round(3.55, 1))
print(round(3.44))
print(round(3.44, 1))
2. math数学库
除了上述直接作用的函数外,math库也提供了更多的数学方法和属性
方法名 | 返回值说明 |
---|---|
.ceil(x) | 返回x向上取整的值 |
.floor(x[, n]) | 返回x向下取整的值 |
.fsum(list) | 返回list中所有元素的和 |
属性名 | 返回值说明 |
---|---|
.pi | 返回圆周率 |
.e | 返回自然常数 |
# 对小数向上取整,进位取整
print(m.ceil(2.55))
print(m.ceil(2.44))
# 对小数向下取整,截断取整
print(m.floor(2.55))
print(m.floor(2.44))
# 获取列表中所有元素的和
print(m.fsum([1, 2, 3, 4, 5]))
# 获取pi,圆周率的值
print(m.pi)
# 获取e,自然常数,无限不循环小数
print(m.e)
36. 实训.璀璨星空
1. 实战内容
利用随机、海龟图、函数等知识点,绘制一张包含很多个五角星的星空
# 引入库
import turtle as t
import random as r
# 绘制速度
t.speed(0)
# 设置窗体大小
t.setup(800, 600)
# 晚间天空SlateBlue4
t.bgcolor("SlateBlue4")
# 五角星函数
def drawStar(x, y, color, size, angle):
t.penup()
t.goto(x,y)
t.pendown()
# 设置颜色
t.color(color)
# 设置角度
t.left(angle)
# 五角星绘制
t.begin_fill()
for i in range(5):
t.forward(size)
t.right(144)
t.end_fill()
# 循环100次
for i in range(100):
# 随机坐标
x = r.randint(-400, 400)
y = r.randint(-300, 300)
# 随机颜色
color = r.choice(["red", "blue", "pink", "white", "green", "orange", "maroon"])
# 随机大小
size = r.randint(10, 30)
# 随机角度
angle = r.randint(0, 360)
# 调用五角星函数
drawStar(x, y, color, size, angle)
# 绘制完毕
t.done()
t.hideturtle()
37. 教培.亲子.学习路线
1. 学习路线
36课语法基础已经结束了!下面就谈一谈教培老师和亲子家长如何规划学习
-
路线规划一(一般小学生):
- 对于能力一般的学员,36课就是他们的归宿,不用继续往下学;
- 36课之后,进入 百题速刷(数理解题)、**海龟绘图(竞赛操作题)**两个篇章;
- 36课在1.5-2小时的线下课,可以填充15节左右。加上百题速刷和海龟绘图,可以填充30多节;
- 也就是说,这条路线完美解决48节一年课程;然后可以考虑考级。。。
- 不太建议第一年比赛,因为选择题部分,涉及到算法、数据结构等内容。。。
- 当然,由于操作题比较合理,而不太正规的赛事的理论题都是拿来主义,自己也不懂。。
- 大概率:给你一个题库,背一背就可以了。然后主攻操作题。
- 大部分学生其实无法进入更高阶段的学习,那继续 用Python 数理解题 和 海龟绘图 运作第二年;
-
路线规划二(优质小学生):
- 从37课后,可以继续学完,到最后。也可以按照路线一,然后再37课往后;
- 而本套课程学完之后,解题 和 绘图 也完毕了,考级也考了,比赛也比了;
- 后续就是 软件开发 和 游戏开发 的各种扩展主题课,直至到中学阶段;
-
路线规划三(优质中学生):
- 优质小学生开始进化优质中学生,更强的数理能力和逻辑能力,此时升级到C++信奥赛体系;
- 这里就开始规划一说的,正规赛事的教学和集训,开始涉及到:
- 数据结构(链表、堆栈、队列、二叉树等等)
- 图论(邻接矩阵、无相连通图、节点等)
- 算法(贪心、递推、二分、枚举、高精度、各种排序等等)
- 初等数学(代数、平面几何、数论、组合数学等)
- 当然,这是后话!!!
38. 做个小软件.Tkinter入门
1. Tkinter初步
Tkinter是一款Python内置的GUI软件界面开发库,用这个库可以做软件
-
学习这款库,有以下注意点:
- 由于是针对小学生5年级+,不按成人那种手册式教学
- 也就是说:用到哪里,就只讲哪里,并且不涉及原理
- 如果对软件开发感兴趣,后续独立主题课会开设项目式课程
- Tkinter和Pygame只花5-6节蜻蜓点水一下,做个了解,学个概念,跟做就行
- 系统性和软游项目课程会做独立课程,基础课中的内容学完啥也做不了的
- 手册地址:https://docs.python.org/zh-cn/3/library/tk.html
-
tkinter库旗下的方法:
方法名 | 说明 |
---|---|
.Tk() | 生成窗体的方法,返回值是一个对象(点出万物的变量),设置当前窗体 |
.Label() | 生成一个文字标签,返回值同上,设置当前文本 |
.mainloop() | 循环窗体,不断刷新窗体内容 |
.Tk()方法下的方法:
方法名 | 说明 |
---|---|
.geometry() | 窗体的大小,长高坐标,具体传参看后续代码 |
.title() | 窗体的标题,参数即标题名称 |
.Label()方法下的方法:
方法名 | 说明 |
---|---|
.pack() | 将文本内容定位到指定的窗体上,细节看后续代码 |
2. 代码详解
通过以上的知识点,完成一个包含文本的窗体
# 引入GUI库 tkinter
import tkinter as tk
# 库下的.Tk()方法,称为窗体组件,可以生成一个窗口
# 注意1:Tk中的T是大写,区分大小写
# 注意2:方法返回一个可以点出万物的变量(是对象,还没学这概念)
# 注意3:可以通过print(type(t))来查看类型
t = tk.Tk()
# 设置窗体大小,800x600是长和高,[400是x轴,200是y轴]
t.geometry("800x600")
#t.geometry("800x600+400+200")
# 设置窗体标题
t.title("tkinter 初步")
# 引入一个Label()文字组件
# 注意1:Label(t[,option])方法组件区分大小写
# 注意2:参数t是必须,text是文字内容
lab1 = tk.Label(t, text="我的第一个tk程序!")
# 入驻窗体
lab1.pack()
# 循环,让窗体内交互内容不断刷新
# 但是,由于我们的编辑器自带了轮询,所以不加也在刷新
# 可是,如果脱离编辑器环境,就变成静态的死窗口了
# 结论,还是加上去
tk.mainloop()
39. 交互.按钮和文本框
1. 按钮和文本框
按钮**.Button()组件方法和单行文本框.Entry()**组件方法的使用
- tkinter库旗下的方法:
方法名 | 说明 |
---|---|
.Button() | 生成一个按钮,参数看后续代码 |
.Entry() | 生成一个单行文本框,参数看后续代码 |
.Entry()旗下的方法:
方法名 | 说明 |
---|---|
.get() | 可以获取单行文本框的值 |
.delete() | 可以删除单行文本框的值,清空直接范围:0-“end” |
.Label()旗下的方法:
方法名 | 说明 |
---|---|
.config() | 可以修改文本内的值 |
- 如何实现按钮点击触发:
- 在Button()按钮内设置时,有一个属性:command
- 将一个函数fn赋值给command属性,即可完成点击按钮触发函数
2. 代码详解
通过以上的知识点,构建一个按钮和输入框,并实现交互
# 创建一个文本框
# 注意1:.Entry()组件方法,区分大小写
ent1 = tk.Entry(t, font=("宋体", 20))
ent1.pack()
# 自定义函数
def fn():
#print("感谢点击!")
# 获取文本框的值
res = ent1.get()
# 改变文本标签的值
lab1.config(text=res)
# 清空文本框
# 0表示第一个字符,"end"表示到最后一个字符
ent1.delete(0, "end")
# 创建一个按钮
# 注意1:.Button()组件方法,区分大小写
# 注意2:command是交互属性,可以调用自定义函数
but1 = tk.Button(t, text="确认", command=fn)
but1.pack()
40. 合法性.验证和弹窗
1. 验证数据
可以通过以往的只是了判断输入框的数据是否符合要求
# 判断res是否为空,当然可以判断各种其它验证
if res == "":
print("数据不可以为空!")
else:
lab1.config(text=res)
ent1.delete(0, "end")
2. 弹窗组件
弹窗组件是一个需要引入的独立库,通过这个组件可以实现弹窗效果
- tkinter.messagebox库旗下的方法:
方法名 | 说明 |
---|---|
.showinfo() | 提示框的类型设定为:普通提示 |
.showwarning() | 提示框的类型设定为:警告提示 |
.showinfo()和showwarning()方法旗下的属性:
方法名 | 说明 |
---|---|
title | 提示的标题 |
message | 提示内容的详情文本 |
# 引入信息提示框
import tkinter.messagebox as tmb
# 判断res是否为空,当然可以判断各种其它验证
if res == "":
#print("数据不可以为空!")
# 提示组件有集中类别:showinfo(普通消息)、showwarning(警告消息)等...
tmb.showwarning(title="警告信息", message="输入框不可以为空!")
# 让输入框获得焦点
ent1.focus()
else:
lab1.config(text=res)
ent1.delete(0, "end")
41. 载入天空.绘图组件
1. 绘制天空
绘图组件:Canvas()提供了一些简单的绘图功能
- tkinter库旗下的方法:
方法名 | 说明 |
---|---|
.Canvas() | 在窗体上绘制图形,可以设置bg、width和height |
.Canvas()方法旗下的方法:
方法名 | 说明 |
---|---|
.create_oval(x1,y1,x2,y2[,options]) | 创建一个圆的图形,前四个参数为左上角和右下角坐标位置 |
.move(oval, moveX, moveY) | 将绘图区的一个图形,移动相应坐标的数值 |
# 载入绘图区域
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,即无法最大化 |
# 移动太阳
cv.move(oval, 0, 100)
# 无法调整大小,x和y轴
t.resizable(False, False)
42. 下拉的魅力.菜单组件
1. 设计菜单
软件有一个重要的功能,就是菜单系统:Menu()组件可以实现
tkinter库旗下的方法:
方法名 | 说明 |
---|---|
.Menu() | 创建一个菜单组 |
# 创建第一个菜单容器,用于存放具体的菜单组
one = tk.Menu(t)
# 再创建一个菜单组,内嵌到one菜单容器里
# 注意1:第一参数,内嵌到哪个菜单容器
# 注意2:tearoff只有两个值1和0,1表示可独立分离,0不可以
file = tk.Menu(one, tearoff=0)
.Menu()旗下的方法:
方法名 | 说明 |
---|---|
.add_cascade() | 在指定的菜单组中添加一个菜单项 |
.add_separator() | 创建一条水平线 |
# 在one这个菜单容器中添加一个"文件"菜单项,设置它隶属于file菜单组
one.add_cascade(label="文件", menu=file)
file.add_command(label="新建")
file.add_command(label="打开")
# 水平分割线
file.add_separator()
file.add_command(label="保存")
# cmmand执行执行函数
file.add_command(label="关闭", command=close)
.Tk()旗下的方法:
方法名 | 说明 |
---|---|
.destroy() | 关闭窗口 |
.config() | 将组件展现 |
# 将菜单容器one展现出来
t.config(menu=one)
# 关闭
def close():
t.destroy()
2. 总结
对Tkinter库的一些说明
- 基础课程点到为止,学完了解即可,也无法真正开发软件(所需的知识点非常多的)
- 后续主题课会通过各种应用软件的开发给有兴趣的同学选择性系统学习
43. 做个小游戏.Pygame入门
1. Pygame初步
Pygame库是一款专门用于游戏开发的库,用这个库可以做游戏
- 学习这款库,有以下注意点:
- 由于是针对小学生5年级+,不按成人那种手册式教学
- 也就是说:用到哪里,就只讲哪里,并且不涉及原理
- 基础课程点到为止介绍Pygame,但主题课只会讲解Pygame zero
- Pygame zero进行了简化封装,更利于小朋友学习研究
- 官方文档翻译中文版:https://blog.csdn.net/Enderman_xiaohei/article/details/87708373
pygame库旗下的方法:
方法名 | 说明 |
---|---|
.init() | 初始化pygame模块及检查系统 |
.display | 属性,控制窗口和屏幕的模块 |
.display属性旗下的方法:
方法名 | 说明 |
---|---|
.set_mode() | 设置窗体的大小,传递元祖参数,返回Surface模块 |
.set_caption() | 属性,控制窗口和屏幕的模块 |
.flip() | 刷新屏幕 |
.update() | 刷新屏幕部分内容,在不传参时:同上 |
Surface模块旗下的方法:
方法名 | 说明 |
---|---|
.fill() | 设置背景颜色 |
2. 代码详解
窗口显示、背景色、标题等设置
# 引入库pygame,并设置别名pg
import pygame as pg
# 初始化
# 注意1:pgame库旗下的方法init()
# 注意2:它的作用是导入pygame各个模块以及检查系统等
pg.init()
# .display属性:控制窗口和屏幕的模块
# .display旗下set_mode()方法,
# 注意1:传递的参数是一个元祖数据,(长, 高)
# 注意2:赋值给screen,这个变量就可以控制窗体和屏幕
# .display旗下set_caption()方法,设置标题
screen = pg.display.set_mode((800, 600))
pg.display.set_caption("Pygame游戏")
# 背景色
bgcolor = "pink"
# 轮询
while True:
# screen旗下的fill()方法,设置背景色
screen.fill(bgcolor)
# pg.display旗下的flip()方法,刷新屏幕
pg.display.flip()
44. 关掉它.event事件
1. 卸载操作quit()
初始化init()加载所需的各种模块,相对应的卸载就是quit()方法
# 卸载模块
pg.quit()
2. event事件入门
事件:即需要通过触发才能执行的程序,比如鼠标点击、键盘按键等触发操作
- 事件如何触发执行?
- 需要不断循环刷新检测,因为鼠标键盘触发是随机时间触发的
- 事件类型也有不少,也需要每次把各种事件循环出来一一检测
pygame库旗下的属性(模块):
模块名 | 说明 |
---|---|
.event | 处理事件和事件列队的模块 |
.event事件模块下的方法:
方法名 | 说明 |
---|---|
.get() | 从事件列队里获取事件 |
.event.get()方法遍历返回值下的属性:
方法名 | 说明 |
---|---|
.type | 获取事件对应的值 |
# 遍历事件
for e in pg.event.get():
# 获取事件对应的值
print(e.type)
- 通过检测关闭窗口对应的事件值,然后卸载pygame
# 遍历事件
for e in pg.event.get():
# 如果检测到事件值为256(即点击了关闭按钮)
if e.type == 256:
# 卸载pygame
pg.quit()
- pygame也提供了常量也对应常用的值:pg.QUIT(这个会返回256)
# pg.QUIT系统常量,直接返回256
if e.type == pg.QUIT:
- 关闭代码编写完毕后,发现会报一个错误:原因是卸载后不认识了
isOpen = True
# 轮询
while isOpen:
screen.fill(bgcolor)
pg.display.flip()
# 遍历事件
for e in pg.event.get():
# 如果检测到事件值为256(即点击了关闭按钮)
# pg.QUIT系统常量,直接返回256
if e.type == pg.QUIT:
isOpen = False
# 卸载pygame
pg.quit()
45. 移动的圆.绘制方法
1. 图形绘制
pygame也自带了各种绘图的方法,我们这里学习一下绘制一个圆以及矩形
pygame库旗下的属性(模块):
模块名 | 说明 |
---|---|
.draw | 绘图模块 |
.time | 用于管理实践的模块 |
.draw模块下的方法:
方法名 | 说明 |
---|---|
.rect() | 绘制一个矩形,参数参考代码 |
.circle() | 绘制一个圆,参数参考代码 |
.time模块下的方法:
方法名 | 说明 |
---|---|
.Clock() | 可以返回一个追踪时间的变量(对象) |
.Clock()方法下的方法:
方法名 | 说明 |
---|---|
.tick() | 设置刷新频率 |
2. 代码详情
绘制一个静态的矩形,绘制一个可以移动的圆,并设置刷新频率
# 时间追踪
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
46. 键盘控制.事件监听
1. 键盘监听
本节课想要通过用户键盘的操作,来改变小球的移动方向
- e.type可以获取到事件的类型:
- 键盘按下时返回值是:768
- 键盘弹起时返回值是:769
- 可以直接判断:e.type == 768
- 或者pygame提供了常量pg.KEYDOWN值为:768
# 判断是否键盘按下
elif e.type == pg.KEYDOWN:
-
而对于上下左右的按键常量为:
- 上:pg.K_UP
- 下:pg.K_DOWN
- 左:pg.K_LEFT
- 右:pg.K_RIGHT
-
但是,判断具体按键并不是e.type,通过手册查询到是e.key
# 小球方向
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:
#print("左")
dir = "left"
elif e.key == pg.K_RIGHT:
#print("右")
dir = "right"
elif e.key == pg.K_UP:
#print("上")
dir = "up"
elif e.key == pg.K_DOWN:
#print("下")
dir = "down"
47. 碰撞检测.坐标重叠
1. 坐标重叠
我们想让移动的球对方块进行碰撞,采用坐标重叠的方法设计
- 如果和判断两个图形重叠:
- 得到方块的坐标位置为:x=>100, y=>100
- 加上方块本身的大小35,算出占用的区域:x(100~135), y(100~135)
- 有些判断希望擦边一点点不算碰撞,那可以设计一个值让判断更加中心一点
# 方块默认位置
rx, ry = 100, 100
while isOpen:
# 方块占用的区域为:x(100~135),y(100~135)
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
- 用类似的方法判断边缘碰撞:
# 判断小球碰撞边界,边缘坐标+本身大小
if x < 0 + 20 or x > 800 - 20 or y < 0 + 20 or y > 600 - 20:
x, y = 390, 290
48. 计算得分.输入文本
1. 文字输入
pygame库提供了文字输入的模块方法font
pygame库旗下的属性(模块):
模块名 | 说明 |
---|---|
.font | 文字模块 |
.font模块下的方法:
方法名 | 说明 |
---|---|
.Sysfont() | 返回一个可以创建字体的变量(对象Font) |
# 设置文本
# 注意1:参数1为字体名称,可以在C盘windows下Fonts查阅
# 注意2:参数2为大小20
font = pg.font.SysFont("simhei", 20)
.Sysfont()方法下方法:
方法名 | 说明 |
---|---|
.render() | 绘制文本 |
Surface模块下的方法:
方法名 | 说明 |
---|---|
.blit() | 将图像或文字图形绘制到窗体上 |
# 得分文本
# 注意1:参数1为文本,参数2为是否平滑抗锯齿
# 注意2:参数3为元祖模式颜色,参数4
# 注意3:blit方法将文本绘制到窗体上
screen.blit(font.render("得分:", True, ("black")), (720,10))
# 分数文本
screen.blit(font.render(str(score), True, ("black")), (780, 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
49. 总裁思维.面向对象
1. 面向过程
之前学习的编程方式就是面向过程,即员工思维。
提醒:面向对象难度较大,对于教培老师,当然要掌握;对于小学生,可选;
- 所谓员工思维:
- 按部就班的完成每一个步骤:比如上班:
- 1.起床 2.洗脸 3.坐公交 4.打卡进公司 5.工作 …等
- 用程序代码理解:自上而下按顺序的一步步完成既定的代码程序;
- 按部就班的完成每一个步骤:比如上班:
2. 面向对象
面向对象就好比总裁思维,那总裁的思维是哪些?
- 所谓总裁思维:
- 按需分配让他人执行所要的步骤:比如上班:
- 1.到公司 2.分配任务 3.完
- 用程序代码理解:以调用的方式,给各个模块发送指令,并执行;
- 按需分配让他人执行所要的步骤:比如上班:
3. 属性和方法
我们之前已经零星的接触过属性和方法的区别
-
属性和方法概念:
- 方法其实就是函数,只不过有一个点语法:pg.init()
- 方法的其实就是:对象.方法() ,一般用于执行某种行为动作
- 而属性,就是不带括号的:e.type
- 属性语法为:对象.属性,一般用于获取某种特性的值
- 备注:我们之前点出万物的变量都可以理解为对象
-
实例演练,小汽车对象:
- 它有哪些属性呢?
- 价格, 大小, 颜色…等等可以理解为属性
- 它有哪些方法呢?
- 行驶, 停靠…等等可以理解为方法
- 获取特性的是属性,执行动作的是方法
- 它有哪些属性呢?
50. 泰拉瑞亚.类与对象
1. 生成概念
有玩过泰拉瑞亚的世界生成形式的游戏吗?大概这个意思
- 理解上图:
- 世界模型可以理解为基石(一切的基础),也可以理解为生产世界的工厂,也是后面“类”这个概念;
- 生成世界123,即:世界工厂生产的行为,英文为new,新建,创建的意思;
- 各种特性的副本,即:对象,对象是具有各种属性和方法的,比如:
- 火山熔岩副本,具有热、灼烧的属性,需要有降温、灭火等方法
- 寒冰洞窟副本,具有冷、冰冻的属性,需要有保暖、碎冰等方法
- 沙漠戈壁副本,。。。。。
- 也就是说,类可以包含全部的功能,而生成对象的时候呢,随机选择特性载入;
- 下面就需要通过代码来实现这个类和对象。
2. 创建类和对象
类是一切的基石,而对象是类生成出来的一个副本
# 编写一个世界类
# 注意1:约定俗成,类名首字母大写
# 注意2:class和pass是关键字
# 注意3:两种写法 class World: 或 class World():
class World:
# 空类会报错,需要pass填充
pass
# 创建一个对象,即副本,直接函数形式调用即可
# 注意1:为方便后续使用,一般会将调用类()返回给一个变量
# 注意2:这个w就是这个对象
# 注意3:World()看上去是函数调用,但有自己的名称:实例化
w = World()
print(w)
print(type(w))
51. 载入特性.创建属性
1. 初始化函数
初始化函数:__ init __() 一般在定义类时定义,也可以称为初始化方法
- 初始化函数作用:
- 如果把类理解成函数,当实例化(创建对象)时,会自动执行初始化函数;
- 初始化函数,也叫初始化方法,在别的语言称为构造方法,这里也可以;
- 参数1是必填参数,固定关键字self,调用本类里的属性和方法用的;
- 那么它的作用到底是什么呢?很简单:在创建对象时自动执行,初始化各项数据;
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
def __init__(self):
# 函数没内容,pass占位
pass
w = World()
1. 类的属性
类的属性,一般用于保证创建的对象具有相应的特性
- 那么,如何创建三个不同特性的世界副本呢?通过初始化函数试试
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
# 注意2:self代表World本身
def __init__(self):
# 创建属性
self.name = "火山"
self.gender = "熔岩"
w1 = World()
print(w1.name+w1.gender,"的世界被创建!")
- 以上的代码只能创建有一个特性的世界副本。如何创建不同的呢?
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
# 注意2:self代表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,"的世界被创建!")
- 结构分析:
- 每个对象都是内存独立分配的空间;
- 每个对象的属性,都是独立的,和其它对象属性毫无关系;
52. 固有特性.静态属性
1. 静态概念
所谓静态,无法实例化改变,数据定义后可以共享给所有对象使用
- 所谓静态属性:
- 静态属性,直接在类的顶部定义,无法通过对象改变的属性;
- 静态属性可以用一个特殊的语法:类.静态属性,而无需实例化对象;
- 什么样的环境需要静态:所有对象的某个属性特性都是同一个值,那就可以静态(共享);
# 世界类
class World:
# 静态属性
# 注意1:不在初始化函数里,不需要self
# 注意2:不通过函数赋值,可直接赋值
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. 动态属性
动态属性,就很好理解了。直接在实例化对象创建的属性
w1 = World("火山", "熔岩")
print(w1.age)
# 实例化对象直接创建的属性
# 类似普通变量,在类中不存在,本身也是实例属性
w1.info = "我是动态属性"
print(w1.info)
- 名词解释:
- 创建属性:一般是在类里声明的属性,比如 self.name 这种;
- 实例属性:实例化后对象调用的属性,比如 w1.name 这种;
- 静态属性:可以用类直接调用,也可以对象调用的属性:World.age 这种;
- 动态属性:类里面没有定义的属性,在实例化后对象直接声明的属性:w1.info 这种;
53. 行动激活.创建方法
1. 实例方法
实例方法(函数),也是普通类里的方法,用于执行实例化对象后的某种行为
# 方法(函数),self是函数必填的
def run(self):
return "遇到火山熔岩副本,我要使用降温灭火装备!"
w1 = World("火山", "熔岩")
# 实例方法
print(w1.run())
w2 = World("寒冰", "洞窟")
print(w2.run())
如果要实现针对不同对象(副本),采取对应的行为:
# 世界类
class World:
age = 1000
def __init__(self, name, gender, a, b):
self.name = name
self.gender = gender
self.a = a
self.b = b
# 类方法(函数),self是函数必填的
# 注意:字符串太长,可以在+号后面加一个\表示换行
def run(self):
return "遇到" + self.name + self.gender +\
"副本,我要使用" + self.a + self.b + "装备!"
w1 = World("火山", "熔岩", "降温", "灭火")
print(w1.run())
w2 = World("寒冰", "洞窟", "保暖", "碎冰")
print(w2.run())
2. 静态方法
和静态属性一样,静态方法是每一个对象(副本)都可以共享使用的方法
# 静态方法
# 在普通方法上方设置@staticmethod就变成静态方法
# 这种@的语法叫做装饰器语法
@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. 动态方法
和动态属性一样,在类外声明的普通函数,并用实例化对象调用的方法
# 普通函数
def eat():
return "吃饭"
# 将函数名赋值给动态属性
w2.eat = eat
print(w2.eat())
4. 类方法
使用装饰器@classmethod声明的方法
- 类方法:
- 和静态方法一样,支持 类.方法() 的调用方式;
- 静态方法只处理数据,无法获取本类的数据,比如无法使用self;
- 类方法必须传递 cls 参数,通过这个 cls 来调用本类的静态属性等;
# 类方法
@classmethod
def info(cls):
return cls.age
# 类方法
print(World.info())
54. 私有属性.封装
1. 封装性
面向对象有三大特性:封装、继承和多态,那什么是封装?
- 封装的解释:
- 对属性进行私有化操作,这种行为就是封装;
- 为何要封装,防止这些属性被污染;
- 怎么理解污染?想一下电脑机箱,为何要把主板、内存、显卡封在壳子里,只留个插孔;
- 我们先看下公有属性,如何赋值和取值的:
# 世界类
class World:
def __init__(self, name, gender):
self.name = name
self.gender = gender
w1 = World("火山", "熔岩")
# 共有属性特性
# 注意1:可以直接赋值
# 注意2:可以直接输出
w1.name = "沙漠"
print(w1.name)
- 将公有属性,转换为私有属性后:
def __init__(self, name, gender):
# 私有属性,只要在前面加上两个下划线即可
self.__name = name
self.__gender = gender
w1 = World("火山", "熔岩")
# 私有属性直接获取,报错
print(w1.__name)
- 封装私有化后,提供一个可以取值的入口方法:
# 世界类
class World:
def __init__(self, name, gender):
# 私有属性,只要在前面加上两个下划线即可
self.__name = name
self.__gender = gender
def getName(self):
return self.__name
w1 = World("火山", "熔岩")
# 私有变量被封装了,但可以设置一个方法作为调用入口
# 这个调用入口就好比机箱提供的插口区
print(w1.getName())
- 封装私有化赋值,有时不是在实例化后赋值的,可以再建立一个赋值入口:
# 世界类
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. 私有方法
私有方法和私有属性概念一样
# 我是私有方法,外部无法调用
def __run(self):
return "运行。。。"
# 无法执行私有方法
print(w1.__run())
- 私有方法也需要一个入口,来执行
# 我是私有方法,外部无法调用
def __run(self):
return "运行。。。"
# 对外公开的方法
def go(self):
return self.__run()
# 启动
print(w1.go())
55. 装饰器.@property
1. @property
getName() 和 setName() 是一种比较通用的封装属性的手段,但还是有点麻烦
- 先补遗下容易出错的点:
# 世界类
class World:
def __init__(self, name = "火山", gender = "熔岩"):
self.__name = name
self.__gender = gender
# 对外获取私有属性__name
def getName(self):
return self.__name
w1 = World()
# 私有属性无法直接赋值取值,但操作语法并不报错
# 这里的__name已经是动态属性,和类里面的私有属性无关
w1.__name = "寒冰"
# 这里输出的是动态属性 寒冰
print(w1.__name)
# 之类获取到私有属性 火山
print(w1.getName())
- 问题分析:
- 私有属性赋值有两种,一种就是 初始化函数传参,第二种就是 setName() 方法;
- 我们希望属性在对象这里,直接用属性赋值取值,可读性更好一些;
- 所以,需要使用装饰器@property来完成这种功能,具体如下:
# 世界类
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())
56. 第二大特性.继承
1. 继承概念
父亲有100w,儿子继承了父亲的100w,那么儿子就有了100w,这就是继承
- 继承扩展理解:
- 世界类或世界工厂,在生成火山、寒冰、沙漠副本时,逻辑都在本类或本工厂里;
- 当这些副本需求内容过多,或逻辑逐步复杂时,世界工厂内的代码将变得冗余难以理解;
- 尤其是当需要通过实例化调用时传递不同参数时,也会变成杂乱可读性差;
- 那么,我们将这个工厂开始拆分,分为三个工厂:火山工厂、寒冰工厂、沙漠工厂;
# 火山类
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() 战斗方法,由于拆分后,又冗余了;
57. 复制共性.改写特性
1. 子类继承
要解决火山、寒冰等工厂冗余问题,我们还得请回世界工厂来整合共性
# 世界类(父类)
class World:
# 将相同的逻辑放在父类继承
age = 1000
def fight(self):
print("在"+ self.name + self.gender +"里战斗!")
# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
class Fire(World):
name = "火山"
gender = "熔岩"
# 寒冰类(子类)
class Cold(World):
name = "寒冰"
gender = "洞窟"
# 火山对象
f = Fire()
print(f.age)
f.fight()
# 寒冰对象
c = Cold()
c.fight()
2. 改写特性
有时,我们需要修改父类继承下来的属性和方法,以及初始化函数
- 初始化函数:
- 上面为了方便理解,均用了静态属性;
- 本次我们尝试用初始化函数的声明的属性试一试;
# 世界类(父类)
class World:
# 将相同的逻辑放在父类继承
age = 1000
# 初始化函数
def __init__(self, name, gender):
self.name = name
self.gender = gender
def fight(self):
print("在"+ self.name + self.gender +"里战斗!")
# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
class Fire(World):
pass
# 寒冰类(子类)
class Cold(World):
pass
# 火山对象
f = Fire("火山", "熔岩")
print(f.age)
f.fight()
# 寒冰对象
c = Cold("寒冰", "洞穴")
c.fight()
- 如果取消掉实例化的传递参数,开始改写属性:
# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
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()
- 那么,如何改写方法呢?
# 重写方法
def fight(self):
#调用父类
World.fight(self)
print(123)
58. 继承中的抽象化
1. 抽象化
对于父类,我们有几个问题有待解决,比如父类要不要封装等
-
对于继承的思考:
- 首先,父类能不能实例化,答案:现在能,但我们希望不能;
- 因为,我们只是希望使用子类创建对象副本,父类只是提供规范;
- 或者,用好理解的语言表述:总裁只是下达命令,真正执行的是员工;
- 员工按照命令去执行即可,但员工自然也有自己独有的特性罢了;
- 所以,在父类中,有些属性和方法,我们只希望提供规范让子类实现:
- 而这种属性和方法称为:抽象属性 和 抽象方法
-
创建一个抽象的世界类,防止用户实例化:
# 引入abc模块,实现抽象化
import abc
# 世界类(抽象父类)
class World(metaclass=abc.ABCMeta):
# 抽象方法
@abc.abstractmethod
def fight(self):pass
# 无法实例化
w1 = World()
- 创建一个子类去继承抽象父类,实例化后强制要求实现抽象方法:
# 火山类(子类)
class Fire(World):
pass
# 实例化后,报错,提示必须实现父类的抽象方法
# 也就是,总裁的命令必须完成
f = Fire()
# 火山类(子类)
class Fire(World):
def fight(self):
return "战斗!"
f = Fire()
print(f.fight())
- 最后一个抽象属性定义:
# 引入abc模块,实现抽象化
import abc
# 世界类(抽象父类)
class 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())
- 为了更加直观,改写一下import引入方式:
# 我们只用abc模块中的两个方法,这样可以去掉abc.的前缀
from abc import ABCMeta, abstractmethod
# 世界类(抽象父类)
class World(metaclass=ABCMeta):
#抽象属性
@property
@abstractmethod
def name(self):pass
# 抽象方法
@abstractmethod
def fight(self):pass
59. 第三大特性.多态
1. 多态的概念
多态:即多种形态,执行同一种命令根据场景不同而执行不同的行为特征
-
多态理解:
- go()这个方法,“走”的意思,但环境不同意思不同:
- 在餐厅前go(),表示吃饭;
- 在校门前go(),表示上课;
- 在网吧前go(),表示打游戏;
- 那么方法,肯定是固定的,那么就必须实现子类重写父类;
- 而调用者也是固定的,否则叫什么多态?
- go()这个方法,“走”的意思,但环境不同意思不同:
-
第一种做法,通过固有函数作为执行者,传入不同环境实现不同行为的多态;
from abc import ABCMeta, abstractmethod
# 世界类(抽象父类)
class 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)
'''
- 第二种做法,通过普通父类的方法去执行子类的方法:
# 世界类(父类)
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等判断操作。
60. 面向对象工具箱
1. 工具箱合集
面向对象中,有很多工具方法来更方便的查询或构建我们的代码
- 一般带有前后两个下划线的方法,称为魔术方法:
- __ init __ () :初始化函数,或称为构造方法,当实例化时
- __ str __ () :打印对象时,替代原本的描述信息
- __ del __() :析构函数,和 初始化 正好相反,当删除实例化对象时触发
# 世界类
class World:
def __init__(self, x, y):
self.x = x
self.y = y
# __str__() 替代本来的对象描述
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()函数
# 世界类(父类)
class World:
def run(self):
return "World!"
# 火类
class Fire(World):
def run(self):
# 通过 super()函数调用父类方法
return super().run() + "Fire!"
f = Fire()
print(f.run())
61. 实训.塞尔达传说①
1. 需求分析
从本节课开始,我们用面向对象实战一款经典的开放世界文字版:塞尔达传说
- 具体要求如下:
- 游戏开始,醒来,系统让你输入用户名;
- 当然,不管你输入什么(除了林克),系统都会纠正你叫“林克”;输入正确,不会纠正;
- 你走出洞穴,生命体只有1颗星,遇到烤火老爷爷,要给你两个烤苹果;
- 你接受吃了烤苹果,则变成3颗星,你走入寒冰区,老爷爷要给你防寒服,你可以选择;
- 防寒服可以增加你在本区域的防御值,你原本的防御值为1,遇到寒冰怪,血量为1,它先手;
- 对战胜利或失败,失败退出游戏;胜利后,进入火山区,矿工要给你防火服,同上;
- 然后,走入城堡拯救公主,公主介绍Boss,攻击力10000,防御力4000,生命值5000,它先手;
- 她说可以给你一把攻击力8999的光之弓,还加送一张先手祝福之卷轴;胜利或失败;
2. 初始化数据
直接面向对象,对于新手来说不太容易,先面向过程开始,从底层打工开始
# 游戏开始
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))
62. 实训.塞尔达传说②
1. 对战系统
和怪物攻击,默认怪先手,通过数值加减来判断胜利或失败
# 走入寒冰区
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:
# 怪物先手,开始攻击,攻击算法:怪物攻击力 - 玩家防御力
# 玩家的生命值 = 玩家生命总量 - (怪物攻击力 - 玩家区域防御力)
# 吃满buff第一轮:3 - (2 - 2) = 3
# 不吃buff第一轮:1 - (2 - 1) = 0 死了
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. 退出机制
胜利后可继续往下执行,但失败后,要退出整个系统,需要函数处理
# 主要函数
def main():
# 游戏开始
...
# 入口
main()
63. 实训.塞尔达传说③
1. 重复对战
直接继续复制寒冰区的代码即可
# 获取防火服
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和初始化数据
# 公主介绍Boss
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
64. 实训.塞尔达传说④
1. Boss对战
Boss对战和小怪基本一致,主要是否接受先手Buff这个条件
# 互相攻击,直到一方倒下
while True:
# 判断先手
if flag2 == 1:
# 玩家开始攻击
enemy3_life = enemy3_life + enemy3_defence - attack
# 取消先手buff
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
- 员工的编码思维已经做完,后面要改成面向对象的方式。
65. 实训.塞尔达传说⑤
1. 旁白类
文字游戏,太多旁白,我们先从这里入手进行整理吧
# 旁白类
class Aside:
# 开场语音
@staticmethod
def start():
print("Loading...")
print("Loading...")
print("Loading...")
print("游戏开始....")
print("林克,林克,林克,醒一醒....")
# 介绍Boss
@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)
- 然后,在主程序用静态方法来调用这些归类好的方法即可:
# 开场
Aside.start()
#...
Aside.who(name)
Aside.over()
#...
66. 实训.塞尔达传说⑥
1. 人物类
将玩家的数据信息,单独通过一个类来管理调用
# 人物类
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开场时,生成人物对象,在主程序操作人物数据
# 开场语音
@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
- 主程序,涉及到人物的六个属性时,全部加上对象.属性的格式:
# 攻击
p.life = p.life - (enemy1_atk - p.defence_cold)
# 统计目前状态
Aside.status(p.life, p.defence, p.attack)
67. 实训.塞尔达传说⑦
1. 怪物类
将怪物的数据信息,单独通过一个类来管理调用
# 怪物类
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
- 主程序,涉及到怪物的三个属性时,全部加上对象.属性的格式:
# 创建寒冰怪,实例化
en1 = Enemy(1, 2)
# 怪物属性调用
p.life = p.life - (en1.attack - p.defence_cold)
68. 实训.塞尔达传说⑧
1. 战斗类
将小怪和boss战的逻辑,存储到一个单独的类进行管理
# 战斗类
class Fight:
# 和普通怪战斗
@staticmethod
def general(p, en, Aside, defence):
# 互相攻击,直到一方倒下
while True:
# 怪物先手:攻击:怪物攻击力 - 玩家防御力
# 玩家生命值 = 玩家生命总量 - (怪物攻击 - 玩家区域防御力)
# 吃满buff第一轮:3 - (2 - 2) = 3
# 不吃buff第一轮:1 - (2 - 1) = 0 死亡
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
# 和Boss战斗
@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
# 正常手(没有buff,我就是先手,有buff,我就是后手)
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
- 主程序调用的时候,直接传入必要的参数即可:
# 创建熔岩怪
en2 = Enemy("熔岩怪", 1, 2)
# 遇到第二个怪,熔岩怪
Aside.fight(en2.name)
# 战斗
if Fight.general(p, en2, Aside, p.defence) == False: return
# 创建Boss
en3 = Enemy("恶魔", 5000, 10000, 4000)
# 战斗
if Fight.boss(p, en3, Aside, flag2) == False: return
69. 实训.塞尔达传说⑨
1. 场景类
目前四个场景都在主程序中,分成独立类,方便以后扩展升级
- 在main.py中,执行主函数,已经只是调用关系,根据场景一一调用,非常清晰
# 引入类库
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)
- 创建世界的抽象父类,在基础知识部分学习过,其它四个场景,则继承它
from abc import ABCMeta, abstractmethod
from Enemy2 import *
from Fight2 import *
# 世界类(抽象父类)
class World(metaclass=ABCMeta):
# 抽象方法,执行场景
@abstractmethod
def exec():pass
- 子类的构建如下:
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)
- 其余的三个子类,一模一样,不再贴代码。
- 这么做的好处显而易见,扩展场景,只需要在这个类中设计即可
70. 实训.塞尔达传说⑩
1. 启动主程序
观察目前结构,调整重复代码,并实现场景的多态调用,实现总裁式一行启动模式
- 在World类中,增加一个统计数据的方法,来避免每个子类重复操作
# 统计数据
def stats(self, p):
# 统计目前状态
Aside.status(p.life, p.defence, p.attack)
- 子类调用
# 统计目前状态
super().stats(p)
-
Aside类,直接通过World引入,避免前台传递Aside类
-
在World类中,创建一个静态方法,实现多态的调用效果
-
由于return 被封装到子类的方法里了,无法实现退出函数,需要再传递回来
# 静态方法
@staticmethod
def run(obj, p):
return obj.exec(p)
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
# 战斗
if Fight.general(p, en1, Aside, p.defence_cold) == False: return False
- 实现主程序一行调用,可以将总调用程序复制给game.py。然后用mian执行game即可
# 引入game
import game2 as g
# 塞尔达,启动
g.main()