python基础
🐍

python基础

python基础
人生苦短,我用python ----吉多范罗苏姆01.python起源
解释器:运行解释每一行代码,读取翻译执行,一行一行执行。
编译型语言:一次变成可执行语言。
完全面向对象的语言
第一个函数print
print('hello world!')
print('hello python!')
img
注意括号和引号成对出现
在终端中直接调用python解释器加上py文件的名字可直接使用python
关于错误
bug:未能正确执行程序
拼写错误,
语法错误:syntaxerror :invalid syntax(每行代码只完成一个动作比如:print
缩进错误:indentationerror:unexpected ident
python 2.X默认不支持中文3.X支持中文
02.执行python程序的三种方式
1.解释器:python (python2.X的解释器) python3(python3.x的解释器)
CPython:官方版本的c语言解释器()Jython:可运行在java平台
PyPy:python实现的,可支持JIT即时编译
2.交互式:直接在终端中输入python 代开python的shell(适合运行小的语法或局部代码)(缺点:不可以保存)
ipython交互式的shell可以自动补全;自动缩进;支持bash shell
imgXZSWA_PJMG7{R$I3[J7LYF.png)
3.集成开发环境IDE):
python的ide—— pycharm
03.pycharm初始设置
恢复pycharm的初始设置:删除家目录下的.pycharmxxx.x目录其中XXX为版本号 rm -r
新建项目
(1)命名规则:数字编号 01_python基础
命名文件建议只使用小写字母数字和下划线文件名不能以数字开始
Linux中为了方便所有用户使用额外安装的软件,应把软件目录移动到根目录下的/opt文件下设置启动快捷方式:在pycharm的tools中选择创建桌面图标
04.注释的作用
表示单行注释在代码的后边加上单行注释
“”“ ”“” 多行注释
05.算术运算符
+加法运算符 - 减法运算符 * 乘法运算符 / 除法运算符
// 表示取整 % 表示取余数 ** 次方幂
在print("a" * 10)对a做乘法,一次输出10个a 在()中调整运算的优先级
06.程序的执行原理
计算机三大件:
(1)cpu:中央处理器,负责数据的处理/计算
(2)内存:临时存储数据(断电后消失)
(3)硬盘:永久存储数据,速度慢,但空间大
cpu负责程序的执行
which python可以查看python解释器的位置但是是软链接(ln -s)
07.程序的作用
程序就是用来处理数据的!!!变量就是用来存储数据的!!!
程序软件的执行:在运行之前保存在硬盘中,运行之后则保存在内存中(分配内存,其余应用程序不允许占用)
在程序内部,为存储东西而在内存中分配的空间就叫做变量(黑马152章)
08.变量的基本使用
变量名 = 值(左边是变量名,右边是变量中存储的值)每个变量在使用前都必须赋值变量只有在第一次出现时才是定义变量,在后续出现时只是使用之前定义的变量
变量的类型
名称保存的数据存储的数据类型变量的地址
定义变量时不需要指定变量的类型在python中
类型:int型(整数型) str型(字符串即中文必须要有引号) bool型(布尔型只有true和flase即0和1,真假) float型(浮点型小数点)
单步调试确定数据的类型
f9进行程序调试
类型分为两大类:数字型:整型int(在python2.X中还有长整型long:长度)布尔型bool 浮点型float 非数值型:字符串str 列表元组字典
可以使用type在ipython中查看变量的具体类型
不同类型变量的计算
(1)整型之间可以直接计算
如果变量是bool型,TRUE是1,False为0
(2)字符串中计算用+来进行拼接
(3)字符串与整型之间用*重复拼接字符串(数字类型变量字符串之间不能进行其他计算)
变量的输入
输入:就是用代码获取用户通过键盘输入的信息
在python中,获取键盘输入的数据,就需要用到input函数
关于函数
一个提前准备好的功能,可以直接进行使用
print()输出 inpu()输入 type()查看类型
notion image
字符串变量 = input("提示信息":)
notion image
notion image
且默认input里面的内容为str即字符串类型
int可以将“ ”里面的内容转换为整型
列子:苹果增强版
  • *两个字符串之间不能用乘法 *
1.使用到input输入函数(且其为str) 2. 使用float将input的数据类型改为浮点型float
notion image
上述价格和重量各有两个变量,太冗杂,直接将input内容添加到float()中
notion image
改进后优点:节约空间,变量名需要占用内存空间;起名字方便:不需要为中间变量额外取名
变量的格式化输出
%被称为格式化操作符,专门用于处理字符串中的格式
包含%的字符串,被称为格式化字符串
%和不同的字符连用,不同类型的数据需要使用不同的格式化字符
%s表示字符串%d有符号十进制位%f浮点数 %.02f表示小数点后两位%%输出%
notion image
定义一个小数,输出结果为 2%
notion image
09.变量的命名
标识符和关键字&变量的命名规则
1.标识符
标识符就是程序员定义的变量名,函数名(名字需要见名如义)
标识符可以由字母下划线及数字组成(不能以数字开头,不含关键字)
2.关键字
关键字就是python内部已经使用的标识符
关键字具有特殊的功能和含义;不允许定义和关键字相同名字的标识符(如:import)3.变量的命名规则
增加代码的识别和可读性,python中的标识符区分大小写
a = 1;每个单词使用小写字母,单词与单词之间用 ”_“ 连接
驼峰命名法
1.小驼峰:userName 2.大驼峰:UserName
10.判断语句(if)
如果条件成立,才能做某事;如果不满足,则做另外一件事。
判断语句又称分支语句,有”判断“才有”分支
if语句的基本语法
if 判断语句:
条件成立时,做某事
......
(代码的缩进为一个tab键,或者4个空格,,建议使用空格且不要混用)
判断年龄:
定义一个变量存储年龄判断是否满足18岁(>=)满足进入网吧,不满足回家
notion image
if下面带缩进的代码看作一个完整的代码块,执行的顺序
运算符
比较运算符:==:检查两个操作数是否相等,if 是,则条件成立,返回结果 True
!=:检查两个操作数是否不相等,if 是,则条件成立,返回结果 True(在python2.x中还可以用<>来表示不等于)
>>=):检查左边数是否大于大于等于)右边数,if 是,则条件成立,返回结果 True
<<=):检查左边数是否小于小于等于)右边数,if 是,则条件成立,返回结果 True
else 判断条件不成立
if判断的条件
条件成立做某事
else:
条件不成立,则做某事
if 与 else下的代码看作完整的代码块
shift+f10 运行程序
11.逻辑运算
逻辑运算符:
与(and)
条件1 and 条件2 (同时成立返回True)
例子:判断年龄是否正常
notion image
或(or)
条件1 or 条件2 (主要有一个成立则返回结果True)
例子:判断成绩是否通过
notion image
其中and和or是连接符
非(not)
将条件判断取反,真变成假,假变成真
例子:定义布尔型数据判断员工是否属于本公司
notion image
其中not:在开发中,通常希望一些代码不满足时,执行一些代码,使用not
如果在拼接一些复杂的逻辑计算条件时,同样可以使用not(即真太多,用假的来判断)
12.if 语句进阶
if : 判断条件,else 处理条件不成立的情况
elif:若需要增加一些条件,条件不同,需要执行的代码不同时,就可以使用elif(不可以单独使用)if 条件1:
条件1满足执行代码
elif 条件2:
条件2满足执行代码
else:
以上条件都不满足,则执行代码
例子:女人过节
notion image
if 的嵌套
elif的应用场景:同时判断多个条件,所有条件都是平级
但时if的嵌套里面,存在前后关系,在条件成立的执行语句中,再判断if 嵌套的内容,注意缩进if 条件1:
if 条件1基础上的条件2:
条件2满足,执行代码
else:
条件2 不满足时执行代码
条件1 不满足时处理
else:
条件1不满足时处理,执行代码
例子:火车站安检
shift+tab去掉缩进
notion image
综合案例:石头剪刀布(if语句)
明确目标和需求:强化多个条件的逻辑运算
体会import 导入模块
需求:
1.从控制台输入出的拳:1:石头;2:剪刀;3:布
2.电脑随机出拳--假定电脑只会出石头
随机数的处理:使用函数import 导入 random 库,调用random库中的randint(a,b)来随机生成a到b之间的整数,放在顶部位置,下方代码任何时候可以使用
3.比较胜负
使用逻辑运算符or判断
notion image
循环
目标:程序的三大流程
顺序:从上向下,执行代码,
分支:根据条件判断(if),决定执行代码的分支,
循环:让特定代码重复执行
while循环基本使用
break和continue
while循环嵌套
循环语句看作一个独立的代码块,让特定代码重复执行
while 语句的基本语法
初始条件设置(通常是重复执行的计数器
while 条件(判断计数器是否达到目标次数):
条件满足时,做的事情1
条件满足时,做的事情2
......
处理条件(计数器+1)
死循环:未对while循环内的计数器进行处理,忘了修改循环的判断条件,程序持续执行
notion image
赋值运算符
  • *赋值运算符中间不能使用空格,i=I+1
=:c=a+b ,将a+b的值赋值给a
c+=a等效于c =c+a(加法) c//=a等效于c =c//a(取整)%取余
notion image
python的计数方法
自然计数法:从1开始计数
程序计数法:从0开始(默认的循环都是从 0 开始)修改后代码:
notion image
循环计算
0-100累加和:
notion image
0-100 之间所有偶数累加求和:(当 i 是偶数是才进行累加操作)
notion image
break 和 continue
是专门运用于循环的关键字,
break:满足某一条件时,退出循环,不执行后续重复代码,
continue:某一条件满足时,不执行后续重复代码
只针对当前所在循环有效
notion image
while 的循环嵌套
初始条件设置(通常是重复执行的计数器
while 条件 1(判断计数器是否达到目标次数):
条件满足时,做的事情1
条件满足时,做的事情2
......
while 条件 2(判断计数器是否达到目标次数):
条件满足时,做的事情1
条件满足时,做的事情2
......
处理条件(计数器+1)
例子:九九乘法表
print 函数会自动增加换行,则可以在输出内容后面加上,end=""就不会有换行效果且“”中内容可以自定义
notion image
九九乘法表
字符串中的转义字符:
\t : 在控制台输出一个制表符,协助在输出文本时保持垂直方向对齐\n:在控制台输出一个换行符
\:输出反斜线
\r :回车
'("):输出单(双)引号
notion image
13.函数
函数:把具有独立功能的代码块组织成一个小模块,在需要的时候调用函数使用包含两步:
1.定义函数:--- 封装独立的功能
2.调用函数:--- 享受封装的成果
notion image
注意py文件命名规范:不能以数字开头
函数的定义
def 函数名():
函数封装的代码
......
(1)def是define的缩写
(2)函数名称应该能够表达函数封装代码的功能,方便后续的调用(3)函数名称的命名应该符合标识符的命名规则
函数的调用:通过函数名() 进行调用
例子:定义打招呼的函数,封装代码
在函数下方进行调用
notion image
不能将函数的调用放在函数的上方,因为python是解释性语言,是一行一行执行的
f7 : step into : 可单步执行代码,会以一行代码来进行处理,会进入函数
(f8:可单步执行代码,step over 会直接略过)
函数的文档注释
定义函数的下方连续敲下三个引号 “”“ ”“”,在调用函数名() 时,按下ctrl + q 对函数的注释内容进行查看
函数的参数
例子:开发一个sum_2_num 的函数
能够实现两个数字的求和功能
函数参数的使用:在函数名的小括号内填写参数,
多个参数之间使用,隔开
notion image
参数的作用
函数把具有独立功能的代码块组织成一个小模块,在需要时调用
函数的参数增加函数的通用性,针对相同的数据处理逻辑,能够适应更多的数据
1. 在函数内部,把参数当作变量使用,进行需要的数据处理
2. 函数调用时,按照函数的定义的参数顺序,把希望在函数内部处理的数据,通过参数传递参数的作用
形参和实参:
形参定义函数时,小括号中的参数,用来接收参数,接受外部数据,在函数内部作为变量使用如def sum_2_num(num1, num2)
实参调用函数时,在小括号的参数,用来把参数传递到函数内部用的 r如 sum_2_num 的 12, 20 是实在的参数,数据
函数的返回值(return)
返回值是函数完成工作后,最后给调用者的一个结果
在函数中可以使用return关键字返回结果
notion image
注意:return表示返回,在 return 后续的代码都不会执行
函数的嵌套调用
一个函数里面又调用另外一个函数,这就是函数的嵌套调用
test2中调用了另外一个函数test2
在执行到调用 test1 函数时,会先执行test1中的任务,执行完后, 回到test2中调用函数test1的位置,继续执行后续的代码
notion image
例子:--打印分割线
需求1:定义一个print_line 函数能够打印 * 组成的分隔线
notion image
打印多行分割线,使用形参在调用函数时使用,使函数更加灵活
notion image
使用 ctrl+q 规范预览
使用模块中的函数
每一个以py结尾的python源文件就是一个模块,使用import进行导入。
notion image
模块名也是一个标识符,服从python的命名规则,不能以数字开头。
pyc(c是compiled编译过)是由python解释器将模块的源码转换为字节码,是为了启动速度优化缓存在“pycache”目录下
14.高级变量类型
列表,元组,字典,字符串
列表(list)
其他语言叫做数组
专门用于存储一串信息,列表用[ ]定义,数据之间用隔开,列表的索引从 0 开始
notion image
列表的常用操作
列表的修改和增加
notion image
列表的删除
# 自带的del删除数据,本质上用来将变量从内存中删除
name="张三"
delname
使用列表自带的方法删除数据
notion image
使用len方法查看列表的长度
notion image
使用列表的排序方法
notion image
关键字是python内置的,具有特殊意义的字符,不需要使用小括号。函数封装了独立功能,可以直接调用(函数名(参数))
方法需要通过对象来调用,表示针对这个对象要做的操作。
列表的循环遍历
迭代遍历来遍历一个列表
notion image
列表的应用场景
列表中存储相同类型的数据
通过迭代遍历在循环体的内容,针对列表中的每一项元素,执行相同的操作
元组(Tuple)
与列表类似,不同在于元组的元素不能修改,不能进行增删改操作
元组表示多个元素组成的序列,在python开发中,有特定的应用场景,可以保存不同类型的数据用于存储一串信息,数据之间使用隔开
元组用()定义
元组的索引从0开始
元组的常用操作
定义元组,如下图所示:
notion image
取值和取索引以及统计数据出现的次数操作
notion image
元组的循环遍历
取值就是从元组中获取指定位置的数据
遍历就是从头到尾依次从元组中获取数据
应用场景
函数的参数返回值,一个函数可以接收任意多个参数,或者一次返回多个数据
格式字符串(%),后面的()本质上是一个元组
notion image
让列表不可以被修改,保护数据的安全
notion image
使用tuple函数可以将list类型的数据转换为元组,同样list函数也可以修改元组为列表使用 tqdm 库可以展示程序运行的进度条
notion image
字典(dictionary)
除开列表外最灵活的数据类型,可以用来存储多个数据类型
通常用于描述一个物体的相关信息
与列表(list)的区别:
列表是有序的对象集合
字典是无序的对象集合,用{}定义,使用键值对存储数据,键值对之间使用分隔。
其中:
键(key):是索引
值(value)数据
键和值必须使用分隔
键必须是唯一的
可以是任何数据类型,但只能使用字符串,数字,或元组
XiaoMing= {"name": "小明",
"gender": "男",
"height": 1.75
}
字典的增删查操作
notion image
字典的其他操作
notion image
字典的循环遍历
使用for 关键字进行迭代
notion image
在迭代中获取到了键 key 后,使用迭代中的变量key来获取字典中的值
notion image
应用场景
使用多个键值对描述一个物体的相关信息描述更复杂的数据信息
多个字典放在一个列表中,再进行遍历,在循环体内部针对每一个字典进行相同的处理
notion image
字符串
字符串就是一串字符,可以用”双引号“定义还可以用单引号‘’定义,大多数都是使用双引号定义字符串
notion image
将字符串中的单个字符迭代,展示单个字符。
len(字符串)获取字符串的长度
字符串.count(字符串)小字符串在大字符串出现的次数
字符串[索引]:从字符串中取出单个字符
字符串.index(字符串)获得小字符串第一次出现的索引
notion image
字符串的其他用法:
判断类型
notion image
isdecimal只能判断整数字,isdigit可以判断unicode类型数字,isnumerical可以判断全部
查找和替换
notion image
notion image
大小写转换
文本对齐
notion image
notion image
notion image
字符串的切片
切片使用索引值来限定范围,从一个大的字符串切出小的字符串
其中:列表和元组都是有序集合,都能够通过索引值来获取去数据
字典则是无序集合,使用键值对来获取和保存数据
切片格式:字符串[开始索引:结束索引:步长]步长就是开始索引加上步长的长度,进行指定位置切片,倒序索引和顺序索引
notion image
公共方法
python的内置函数
len(item): 计算容器中元素的个数
del(item):删除变量,有两种方式(关键字和函数)
max(item):返回容器中的最大值
min(item):返回容器最小值(如果是字典,只针对key比较)ascii码
cmp(item1,item2):比较两个值,但是python3取消了该函数(但是可以使用运算符进行比较,字典与字典不能比较大小,因为字典是无序集合)
切片
使用索引值来限定范围,可以针对列表[ ]元组( )进行切片([索引起始位置:索引结束位置:步长])但是字典{}不能进行切片
运算符
乘法列表和元组可以进行乘法运算,但是字典不能使用乘法运算,因为是键值对存储数据,key是唯一的
加号:支持类型字符串,列表(与列表的extend([1, 2])类似,但不会生成新的列表变量,直接修改,append直接将元素追加到列表中),元组进行合并
in(not in)(成员运算符):判断指定元素是否存在,支持字符串,列表,元组,字典(只判断字典的key
notion image
完整的for循环语法
for 变量 in 集合:
循环体代码
else:
未通过break退出循环,循环结束后,会执行的代码
notion image
应用场景
迭代遍历嵌套的数据类型时,例如一个列表包含了多个字典
需求:要判断一个字典中是否存在指定的值
存在,提示并且退出循环
不存在,在循环体结束后,得到一个统一提示
notion image
使用 break ,在搜索到查找的内容时,会跳出循环,则不会执行for的else下的内容,只有在进行数据搜索时使用else来进行提示用户
综合应用:名片管理系统
目标:通过已学习的知识点
变量,流程控制,函数,模块
来开发名片管理系统
流程
1.框架搭建
1.1 文件准备
新建 main.py 文件,保存主程序功能代码(程序的入口,每次启动该系统都是通过这个文件启动)新建 tools.py 文件,保存所有名片功能函数将对名片的增删改查等功能封装在不同函数中1.2 编写主程序循环
while True:(该循环为无限循环,由用户主动决定程序的退出)
pass 占位符,关键字,保证程序的完整性
“# todo注释”:提示开发者要做的事
notion image
2.保存名片数据的结构
程序就是用来处理数据的,变量就是用来存储数据的
使用字典记录每一张名片的详细信息
使用列表统一记录所有的名片字典
使用shift+f6对变量进行快捷修改
在开发时,对所写的功能可以添加# TODO 注释,在相关功能开发完成后,使用小灯泡生成存根,提示该功能的注释
notion image
Linux的shebang符号( #! )
使用which 加名字查看具体位置,在py文件最顶端加上 #! python的解释器完整路径,(可以在终端中
不需要解释器就能执行)
使用 chmod +x cards_main.py 给该文件赋予可执行权限
notion image
notion image
15.变量的进阶
变量的引用
变量和数据都是保存在内存中的
在python中函数参数传递以及返回值都是靠引用传递的
引用的概念:
(【黑马程序员Python教程_600集Python从入门到精通教程(懂中文就能学会)-哔哩哔哩】)
1. 在python中变量和数据时分开存储的
2. 数据保存在内存中的一个位置
3. 变量保存着数据在内存中的地址
4. 变量中记录数据的地址就叫引用
5. 使用id()可以查看数据在内存中的地址(看到赋值语句,看等号的右侧,使用变量来记录数据的内 存地址)
notion image
函数的参数和返回值的传递
在 python中,函数的实参/返回值都是靠引用来传递的
notion image
可变和不可变类型
不可变
数字类型,字符串,元组tuple(给变量赋值一个新的数据,则变量不对之前的数据进行引用)可变
列表(可以修改列表内容,而不会改变内存地址,调用列表的方法),字典(通过方法改变数据,不会改变引用,即内存中的地址)
notion image
其中可变类型的数据不可以作为字典中键值对的key,不可变类型可以
notion image
哈希
unhashable
在python中有一个叫做hash(0)的函数
接受一个不可变数据作为参数,
返回的结果为一个整数
hash(算法)能够提取指定数据的特征码
notion image
notion image
# 可以使用enumerate()函数来枚举字符串的元素,列表,字典和range对象的元素,都是返回索引和值的元组
a=list(enumerate("abcd"))
print(a)
16 局部变量和全局变量
局部变量:只能在函数内部使用
全局变量:定义在函数外部,所有的函数都可以使用。
局部变量的使用
可以在不同的函数内部使用相同的变量名称
notion image
局部变量的作用:临时保存函数内部需要使用的数据
局部变量的生命周期
1.在函数执行时被创建
2.在函数结束后,局部变量被系统回收
3.在局部变量的生命周期内,可以用来存储函数内部临时使用到的数据。
全局变量的使用
notion image
其他函数都可以访问到外部变量
全局变量:在函数内部定义局部变量num与全局变量num名称相同,但是在函数内部不能修改全局变量num的值,在函数使用时,若函数内部有num,只输出num = 11,在函数结束执行后,num = 11被系统回收,所以在使用函数demo2时,会输出num = 10.
notion image
在函数内部修改全局变量的值
notion image
在定义全局变量时,要将全局变量放在所有函数的上方,以便所有函数都能够访问
notion image
全局变量的起名加上 g_num或者gl_num
函数参数和返回值的作用
如果函数内部的数据不确定,就可以调用外部的变量来作为函数内部的参数函数的返回值根据实际情况而定
函数的返回值
返回值就是函数执行完后,给调用者返回的一个结果
多个返回值(可以使用元组来封装,返回多个值)
notion image
若想处理元组中的单个元素,可以设置全局变量来调用函数,而不是用过索引来精确查找元组中的元素(变量),元组是有序的
notion image
函数的参数进阶
不可变和可变的参数
无论传递的参数是可变还是不可变
只要针对参数使用赋值语句,会在函数内部修改局部变量的引用,不会影响外部变量的引用
notion image
notion image
notion image
:如果传递的参数是可变类型,在函数内部,使用方法修改了数据的内容,同样会影响到外部的数据
notion image
面试题:(运算符:+= )
在python中,列表变量调用+=本质上实在执行列表的方法 extend ,不会修改变量的引用()调用方
notion image
缺省参数
定义函数时,可以给某个参数指定一个默认值,具有默认值的参数就叫做缺省参数调用函数时,如果没有传入缺省参数的值,则在函数内部使用定义函数时指定的参数默认值函数的缺省参数,将常见的值设置为参数的缺省值,从而简化函数的调用
notion image
注意事项
(1)缺省参数的定义位置
必须保证带有默认值的缺省参数在参数列表末尾
notion image
多值参数
定义支持多值参数的函数
有时一个函数能够处理的参数个数是不确定的,可以使用多值参数在python中有两种多值参数
参数名前加一个 * 可以接收元组(tuple)()
参数名前加两个 ** 可以接收字典(dict) { }键值对
给多值参数命名,习惯命名方式
*args:——存放元组参数,前面有一个 *
** kwargs:——存放字典参数,前面有两个 **
notion image
多值参数的案例——计算任意多个数字的和
需求:
1.定义一个函数sum_numbers, 可以接受的任意多个整数
2.功能要求:将传递的所有数字累加并返回累加结果
notion image
元组和字典的拆包
在调用带有多值参数的函数时,若希望:
将一个元组变量直接传递给 args
将一个字典变量直接传递给 kwargs
就可以使用拆包简化参数的传递,拆包的方式就是:
元组变量前增加一个 *
字典变量前增加两个 *
notion image
函数的递归
函数调用自身的编程技巧成为递归
特点:
一个函数内部调用自己
代码特点:
函数内部的代码时相同的,只是针对的参数不同,处理结果不同
当参数满足一个条件时,函数不在执行,(通常被称为递归的出口,针对参数进行判断,否则会死循环
defsum_numbers(num):
print(num)
# 递归的出口,参数满足某个条件时,不在执行函数
ifnum==1:
return
# 在函数内部自己调用自己(递归)
sum_numbers(num-1)
sum_numbers(2)
递归案例----计算数字累加
1,定义一个函数
2,能够接收一个num的整数参数
3,计算1+2+3....+num的结果
defsum_numbers(num):
# 设置出口
ifnum==1:
return1
# 求num + (1+...+num-1)的和
temp=sum_numbers(num-1)
returnnum+temp
result=sum_numbers(2)
print(result)
猴子吃桃问题:猴子第一天摘下若干个桃子,
当即吃了一半,还不瘾,又多吃了一个第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。
到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少?
defmonkey(x):
ifx==1:
return1
num=monkey((x-1) +1) *2
returnx+num
count=sum_numbers(10)
print(count)
面向对象
17.类和对象
01.类和对象的概念
类是对象的抽象,对象是类的实例
notion image
类:对一类事物的描述,是抽象的、概念上的定义。
notion image
对象:实际存在的该类事物的每个个体,因而也称实例(instance)。
对象面向对象编程的两个核心概念。
1.1 :是对一群具有相同特征或者行为的事物一个统称,是抽象的,不能直接使用
特征被称为属性
行为被称为方法
就相当于是创建飞机的图纸,是一个模板,是负责创建对象的
1.2 对象由类创建出来的一个具体存在,可以直接使用
哪一个类创建出来的对象,就拥有哪一个类中定义的方法 属性和方法
对象就相当于用图纸制造的飞机
在程序开发中,应该先有类再有对象
02.类和对象的关系
类是模板对象是根据这个模板创建出来的,应该先有类再有对象
类只有一个,而对象可以有很多个
不同对象之间属性可能会各不相同
中定义了什么属性或方法对象中就有什么属性和方法不可能对也不可能少
03.类的设计
在使用面向对象开发前,分析需求,确定程序中需要包含哪些类。
类的三要素
1.类名:这类事物的名称,满足大驼峰命名法(每个单词首字母大写CapWord)2.属性:这类事物具有什么样的特征
3.方法:这类事物具有什么样的行为
类名的确定
名词提炼法分析整个业务流程出现的名词通常就是找到的类
属性和方法的确定
对象的特征描述,通常可以定义成属性
对象具有的行为(动词),通常可以定义成方法
如果需求中没有设计的属性或者方法在设计类时,不需要考虑
18 面向对象基础语法
目标
dir 内置函数
定义简单的类(只包含方法)
方法中的self参数
初始化方法
内置方法和属性
1.dir内置函数
在 python中对象几乎时无处不在的,变量,数据,函数都是对象
python中的验证方法:
标识符/数据后输入. ,然后按下tab键,ipython会提示对象能够调用的方法列表使用内置函数dir 传入标识符/数据 ,可以查看对象内的所有属性和方法
def demo():
pass
dir(demo)
notion image
2.定义简单的类(只包含方法)
面向对象时更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,可以直接调用这些方法
2.1定义只包含方法的类
class 类名:
def 方法名1(self,参数列表):
pass
......
方法的定义格式和函数几乎一样
2.2创建对象
当一个类完成时,使用这个类来创建对象,语法:
对象变量=类名()
2.3第一个面向对象的程序
需求:
小猫爱吃鱼,小猫爱喝水
分析:
1.定义类cat(猫)
2.定义方法eat(), drink()
3.不需要定义属性,没有要求
# 定义一个猫类
classCat:
# 定义方法爱吃鱼
defeat(self):
print("小猫爱吃鱼")
# 定义方法爱喝水
defdrink(self):
print("小猫爱喝水")
# 创建猫对象
cat=Cat()
# 调用Cat类中的方法
cat.eat()
cat.drink()
引用的概念
notion image
使用同一个类创建出来的不同对象,不是同一个对象
notion image
给Cat类创建属性(特征)
2.4方法中的self参数
在python中给对象设置属性非常简单,但不推荐使用
对象的属性的封装应该封装在类的内部
notion image
2.5 初始化方法(init)
classCat:
defeat(self):
# self中时对cat1对象的引用,及内存地址十六进制
print("%s爱吃鱼"%self.name)
cat1=Cat()
# 给cat1对象赋予name属性
cat1.name="汤姆"
# 调用猫类的eat方法
cat1.eat()
在上述代码中存在问题,给对象赋予属性实在类的外部,不推荐使用,应该将类的属性封装在类里面
当使用类名()创建对象时,会自动执行以下操作:
1.为对象在内存中分配空间 -- 创建对象
2.为对象属性设置初始值 -- 初始化方法(init
该方法是专门用来定义一个类具有哪些属性的方法
初始化方法 __ init __ 方法,是对象的内置方法
改造初始化方法 -- 初始化的同时设置初始值
在开发中,希望在创建对象的同时就设置对象的属性,可以对init方法进行改造 1. 把希望设置的属性值,定义成init方法的参数
2. 在方法内部使用self.属性值 = 形参接收外部传递的实参
3. 在创建对象时,使用类名(属性1,属性2)调用
notion image
2.6 内置方法和属性
del 方法对象被从内存中删除前,会被调用
str 方法返回对象的描述信息
1.在python中当使用类名() 创建对象时,为对象分配完空间后,自动调用init方法
当一个对象被从内存中销毁前,会自动调用del方法
生命周期
一个对象调用类名() 创建,生命周期开始
一个对象的 del 方法一旦被调用,生命周期结束
在对象的生命周期内,可以访问对象的属性,或者让对象调用方法
2. str 方法
使用print输出对象变量,默认输出的是这个变量引用的对象是由哪一个类创建的对象,以及在内 存中的地址(十六进制)
若希望print输出的是自定义的内容,使用 str 方法
注意:使用 str 方法必须返回一个字符串
19.面向对象封装的案例
目标
封装
小明爱跑步
存放家具
1.封装
1. 封装是面向对象编程的一大特点
2. 面向对象编程的第一步 -- 将属性方法封装到一个抽象的类中3. 外界使用类创建对象,然后让对象调用方法
4. 对象方法的细节,都被封装在类的内部
2.小明爱跑步
需求:
1. 小明体重 75 公斤
2. 小明每次跑步会减肥 0.5公斤
3. 小明每次吃东西体重增加 1公斤
分析:小明是一个人类,其中属性包含体重,方法有跑步,吃东西
# 小明体重75公斤
# 小明每次跑步体重减0.5公斤
# 小明每次吃东西体重增加1公斤
classPerson:
"""
小明爱跑步
"""
# 初始化方法,减缓对象的创建
def__init__(self, name, weight):
# self.属性 = 形参
self.name=name
self.weight=weight
# 自定义返回内容
def__str__(self):
return"我的名字叫 %s, 体重是 %.2f kg"% (self.name, self.weight)
defrunning(self):
print("%s 爱跑步"%self.name)
self.weight-=0.5
defeating(self):
print("%s 吃东西"%self.name)
self.weight+=1
XiaoMing=Person("小明", 75.0)
XiaoMing.running()
XiaoMing.eating()
print(XiaoMing)
注意:在对象的方法内部,可以直接访问对象的属性
2.1.小明扩展-- 小美也爱跑步
需求:
1. 小明和小美都爱跑步
2. 小明体重 75公斤
3. 小美体重 45公斤
4. 每次跑步都会减少 0.5 公斤
5. 每次吃东西都会增加 1 公斤
# 小美爱跑步扩展
XiaoMei=Person("小美", 45.0)
XiaoMei.eating()
XiaoMei.running()
print(XiaoMei)
由此看出同一个类中创建的不同对象,其属性内容是互不干扰的
3.摆放家具
需求:
1. 房子有户型,总面积,和家具名称列表
1. 新房子没有任何家具
2. 家具有名字和占地面积,其中:
1. 席梦思占地 4 平米
2. 衣柜占地 2 平米
3. 餐桌占地 1.5 平米
3. 将以上三件家具添加到房子中
4. 打印房子时,要求输出:户型,总面积,剩余面积,家具名称列表
注:被使用到的类,应该先开发
# 定义家具类
classHouseItem:
def__init__(self, name, area):
self.name=name
self.area=area
def__str__(self):
return"[%s]占地%.2f平米"% (self.name, self.area)
# 定义房子类
classHouse:
def__init__(self, house_type, area):
self.house_type=house_type
self.area=area
# 剩余面积
self.free_area=area
# 家具名称
self.item_list= []
def__str__(self):
# python能够将一对小括号里面的内容连接在一起,避免代码过长 return (
"户型:%s\n "
"总面积:%.2f平米[剩余面积:%.2f平米]\n " "家具列表:%s"
% (self.house_type,
self.area,
self.free_area,
self.item_list)
)
# 添加家具方法
defadd_item(self, item):
print("要添加:%s"%item)
# 1.判断家具的面积
ifitem.area>self.free_area:
print("%s的面积太大,无法添加"%item.name)
return
# 2.将家具的名称添加到列表中
self.item_list.append(item.name)
# 3.计算剩余的房屋面积
self.free_area-=item.area
20.面向对象封装案例||
士兵突击案例
身份运算符
| 一个对象的属性,可以是另外一个类创建的对象
需求:
1. 士兵许三多有一把ak47
2. 士兵可以开火
3. 枪能够发射子弹
4. 枪装填子弹 -- 增加子弹数量
士兵(soldier类) ak47(gun类)
# 开发枪类
classGun:
# 初始化方法,简化对象的创建
def__init__(self, gun_name):
# 1.枪的类型,由外界传递
self.gun_name=gun_name
# 2.枪的子弹,默认为0,发射子弹,则调用装填子弹的方法,则不需要定义形参self.bullet_count=0
# 枪装填子弹
defadd_bullet(self, count):
self.bullet_count+=count
# 子弹足够,则枪可以发射子弹
defshoot(self):
# 1.判断子弹数量
ifself.bullet_count==0:
print("%s子弹不够,请装填子弹"%self.gun_name)
return
# 2.子弹足够,则发射子弹,-1 操作
self.bullet_count-=1
# 3.提示发射信息
print("bang, [%s]的剩余子弹[%d]"% (self.gun_name, self.bullet_count))
士兵类假设
每个新兵都没有枪(gun)士兵则没有gun这个属性,
则在初始化定义属性的时候,不确定设置的值,则可以设置为None
1. None 关键字表示什么都没有
2. 表示一个空对象,没有方法和属性,是一个特殊的常量
3. 可以将 None 赋值给任何一个变量
# 2.开发士兵类
classSoldier:
# 定义新兵的属性,但新兵没枪,但有该属性
def__init__(self, name):
# 1.新兵的姓名
self.name=name
# 2.新兵拥有的枪
self.gun=None
# 士兵可以开火
deffire(self):
# 1.士兵开枪,判断要有枪,
ifself.gunisNone:
print("[%s]没有发枪"%self.name)
return
# 2.装填子弹,
self.gun.add_bullet(50)
# 3.扣动扳机,调用枪类里面的方法
self.gun.shoot()
# 1.创建枪对象
ak47=Gun("ak47")
# 枪不能自己装填与发射子弹
# ak47.add_bullet(50)
# ak47.shoot()
# 2.创建士兵对象
XuSanDuo=Soldier("许三多")
# 使用赋值语句,将枪给许三多,即使用调用
XuSanDuo.gun=ak47
XuSanDuo.fire()
# print(XuSanDuo.gun)
身份运算符
身份运算符用于比较两个对象的内存地址是否一致 -- 是否对同一个对象的引用
在python 中针对 None 比较时,建议使用is判断
is 判断标识符是否引用同一个对象 x is y即 id(x) = id(y)
is not 判断标识符是否引用不同的对象, x is not y即id(x) != id(y)
ifself.gunisNone:
ifself.gun==None:
# 使用is 比较内存地址是否相等
# 使用 == 判断值是否相等
is 与 == 的区别
1. is 用于判断两个变量引用对象是否为同一个
2. == 用于判断引用变量的值是否相等
21.私有属性和私有方法
应用场景:
1. 实际开发中,对象的某些属性或者方法只希望在对象内部被使用,而不希望再外部被访问 2. 私有属性就是对象不希望公开的属性
3. 私有方法就是对象不希望公开的方法
定义方式:在定义属性或方法时在属性名或者方法名前增加两个下划线,定义的就是私有属性的方法
__name
classWomen:
def__init__(self, name):
self.name=name
# 私有属性,在属性名前加上 __
self.__age=18
def__secret(self):
print("%s 的年龄是 %d"% (self.name, self.__age))
defdetail(self):
self.__secret()
Jane=Women("简")
# 私有属性的特点,只能在类的内部访问,在外部访问不到
# print(Jane.__age)
# 私有方法,同样不能再外部进行访问
# Jane.__secret()
Jane.detail()
伪私有方法和私有属性
在日常开发中,不要使用这种方式,访问对象的私有属性和私有方法
Python中,没有真正的私有
1. 在给属性,方法命名时,实际是对名称做了一些特殊处理,是的外界无法访问到 2. 处理方式:在名称前加上 _类名 => _类名__名称
# 伪私有属性
print(Jane._Women__age)
# 伪私有方法
Jane._Women__secret()
22.继承
目标:
1. 单继承
2. 多继承
面向对象三大特性
1. 封装根据职责属性方法封装到一个抽象的类中类=>对象
2. 继承实现代码的重复调用,相同的代码不需要重复的编写
3. 多态不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度继承的语法
class 类名(父类名):
pass
在子类中只需要根据子类的属性进行开发
专业术语
继承:狗类是动物类的子类,动物类是狗类的父类,狗类是从动物类继承
派生:狗类是动物类的派生类,动物类是狗类的基类
classAnimal:
defeat(self):
print("吃")
defdrink(self):
print("喝")
defrun(self):
print("跑")
defsleep(self):
print("睡")
classDog(Animal):
defbark(self):
print("叫")
1.继承的传递性
1. 子类拥有父类以及父类的父类中封装的所有属性和方法
classXiaoTianQuan(Dog):
deffly(self):
print("飞")
wangcai=XiaoTianQuan()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
wangcai.fly()
方法的重写
1. 子类拥有父类的所有方法和属性
2. 子类继承父类,可以直接使用父类中封装的方法,不许再次开发
应用场景:
当父类中封装的方法不能满足子类的需求时,可以对方法进行重写
重写的两种情况
1. 覆盖父类的方法
2. 对父类方法进行扩展
1.覆盖父类的方法
1. 若在开发中,父类的方法实现和在子类的方法实现,完全不同2. 可以使用覆盖的方式,在子类中重新编写父类的方法实现
就相当于在子类中定义一个和父类同名的方法并且实现
重写之后,只会调用子类中重写的方法
classXiaoTianQuan(Dog):
# class XiaoTianQuan(Dog, Cat):
defbark(self):
print("我可不会狗叫")
defrun(self):
print("我tm直接起飞")
deffly(self):
print("飞")
wangcai=XiaoTianQuan()
wangcai.run()
wangcai.sleep()
# 如果子类中重写父类方法,调用子类重写的方法
2.对父类方法进行扩展
1. 如果在子类的方法实现中,包含父类的方法实现
1. 父类原本封装的方法是子类方法的一部分
2. 就可以使用扩展的方式
1. 在子类中重写父类的方法
2. 在需要的位置使用.super().父类方法来调用父类方法的执行
3. 代码其他的位置针对子类的需求,编写子类特有的代码实现
关于super
1. 在python中 super 是一个特殊的类
2. super() 就是使用super 类创建出来的对象
3. 最常使用的场景就是重写父类的方法时调用父类中封装的方法实现
classXiaoTianQuan(Dog):
defbark(self):
# 1. 针对子类特有的需求,编写代码
print("我不会狗叫")
# 2. 使用super(). 调用原本在父类中的封装方法
super().bark()
# 3. 增加其他子类的方法
print("ababa")
另外一种
在python2.X中调用父类的方法
父类.方法(self)
Dog.bark(self)
如果使用子类调用方法,会出现递归调用,没有出口会出现死循环
父类的私有方法和私有属性
1. 子类对象不能在自己的方法内部直接访问父类的私有属性或方法
2. 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
1. 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
2. 私有属性、方法,通常用于做一些内部的事情
classA:
def__init__(self):
self.num1=100
self.__num2=120
def__test(self):
print("私有方法 %d %d "% (self.num1, self.__num2))
classB(A):
defdemo(self):
# 1.在子类的对象方法中, 不能访问父类的私有属性
# print("访问父类的私有属性 %d" % self.__num2)
# 2.在子类的对象方法中,不能调用父类的私有方法
# self.__test()
pass
# 1.创建子类对象
b=B()
print(b)
b.demo()
# 在外界不能直接访问对象的私有属性或调用私有方法
# print(b.__num2)
# b.__test()
# a = A()
# 伪私有方法
# a._A__test()
在子类中访问到父类的公有属性和公有方法
子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
classA:
def__init__(self):
self.num1=100
self.__num2=120
def__test(self):
print("私有方法 %d %d "% (self.num1, self.__num2))
deftest(self):
print("父类的公有方法调用私有属性 %d"%self.__num2)
self.__test()
classB(A):
defdemo(self):
# 1.在子类的对象方法中, 能访问父类的公有属性
print("父类的公有属性 num1=%d"%self.num1)
# 2.在子类的对象方法中,能调用父类的公有方法
self.test()
# 1.创建子类对象
b=B()
print(b)
b.demo()
2.多继承
概念:
子类可以有多个父类,继承所有父类属性和方法
多继承的语法格式:
class 类名(父类名,父类名):
classA:
defdemo(self):
print("父类demo方法")
classB:
defdemo1(self):
print("父类demo1方法")
# 多继承
classC(A, B):
"""
好处:多继承可以让子类对象,同时具有多个父类对象的属性和方法 """
pass
c=C()
c.demo()
c.demo1()
多继承的注意事项
若不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法?
在开发时,尽量避免这种混淆的情况。
存在同名的方法尽量避免使用多继承
Python中的 MRO -- 方法搜索顺序
python 中针对类提供了一个内置属性, mro 可以查看方法搜索顺序
MRO 是 method resolution order,主要用于在多继承时判断方法、属性的调用路径
# 确定C类对象调用方法的顺序
print(C.__mro__)
输出结果
(<class'__main__.C'>, <class'__main__.B'>, <class'__main__.A'>, <class 'object'>)
1. 在搜索方法时,是按照 mro 的输出结果从左至右的顺序查找的2. 在当前类中找到方法就执行,不在搜索
3. 若没找到,则查找下一个类,找到则执行,不在搜索
4. 若找到最后一个类,未找到,则程序报错
新式类与旧式类
object是 python为所有对象提供的基类,提供一些内置的属性和方法,使用 dir函数查看
1. 新式类:以 object 为基类,推荐
2. 旧式类:不以object为基类,不推荐
python3中定义类时,若没指定父类,则默认使用 object 作为该类的基类 -- 在python3的类都是新式类
python2中未指定父类时,则不会以object为基类
新式类和旧式类在多继承时,-- 会影响方法的搜索顺序
无论在python3或2中定义类时,都建议使用如下语法格式:
class A(object):
pass
主动继承object基类,使用新式类
23 .多态
面向对象的三大特性
1. 封装:根据职责将属性和方法封装到一个抽象的类中
2. 继承:实现代码的重用
3. 多态:不同的子类对象调用相同的父类方法产生不同的执行结果 1. 多态:可以增加代码的灵活度
2. 以继承重写为前提
3. 是调用方法的技巧,不会影响到类的设计
多态的案例演练
1.在dog类中封装方法game
普通狗只是简单的玩耍
2.定义XiaoTianQuan继承自Dog,并且重写game方法
哮天犬需要在天上玩耍
3.定义Person类,封装一个和狗玩的方法
在方法内部,直接让狗对象调用game方法
classDog(object):
def__init__(self, name):
# 定义狗的name属性
self.name=name
defgame(self):
print("%s 蹦跳着玩耍"%self.name)
classXiaoTianQuan(Dog):
defgame(self):
print("%s 在天上玩耍"%self.name)
classPerson(object):
def__init__(self, p_name):
self.p_name=p_name
defgame_with_dog(self, dog):
print("%s 和 %s 一起愉快的玩耍"% (self.p_name, dog.name))
# 让狗玩耍
dog.game()
# 1.创建狗对象
WangCai=Dog("旺财")
XiaoTian=XiaoTianQuan("哮天犬")
# 2.创建人对象
XiaoMing=Person("小明")
# 3.调用和狗玩的方法
XiaoMing.game_with_dog(WangCai)
XiaoMing.game_with_dog(XiaoTian)
1.类的结构
1.1术语
1. 使用面向对象开发时,第一步设计类
2. 使用类名()创建对象,创建对象的动作有两步
1. 在内存中为对象分配存储空间
2. 调用初始化方法 init 为对象初始化
3. 对象创建后,内存中就有一个对象的实实在在的存在---- 实例
因此:
1. 创建出来的对象也叫做类的实例
2. 创建对象的动作叫做实例化
3. 对象的属性叫做实例属性
4. 对象调用的方法叫做实例方法
1.2类是一个特殊的对象
python中一切皆对象
class A: 定义的类属于类对象
obj1 = A():属于实例对象
1. 在程序运行时--类同样会被加载到内存
2. 类是一个特殊的对象 -- 类对象
3. 程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多实例对象
4. 除了封装实例的属性和方法外,类对象还可以有自己的属性和方法
1. 类属性
2. 类方法
通过类名.的方式访问类的属性调用类的方法
类属性和实例属性
概念:
1. 类属性就是给类对象中定义的属性
2. 通常用来记录与这个类相关的特征
3. 类属性不会用于记录具体对象的特征
实例需求:
1. 定义一个工具类
2. 每个工具都有自己的名字
3. 需求:知道这个工具类创建了多少个实例对象
classTool(object):
# 定义类属性,适用赋值语句,记录所有工具对象的数值
count=0
def__init__(self, name):
self.name=name
# 让类属性的值+1
Tool.count+=1
# 1.创建实例对象
tool1=Tool("斧头")
tool2=Tool("锤子")
tool3=Tool("尺子")
# 2.输出工具对象的总数
print(Tool.count)
属性获取机制
在python中属性获取存在一个向上查找机制
使用对象名.(类属性) 如tool1.count(
1. 首先在对象内部查找对象属性
2. 没有找到就向上查找类属性)
访问类属性的两种方式:
1. 类名.类属性
2. 对象.类属性
因为使用对象.类属性 = 值
classTool(object):
# 定义类属性,适用赋值语句,记录所有工具对象的数值
count=0
def__init__(self, name):
self.name=name
# 让类属性的值+1
Tool.count+=1
# 1.创建实例对象
tool1=Tool("斧头")
tool2=Tool("锤子")
tool3=Tool("尺子")
# 使用对象.类属性的问题
tool3.count=2
print("工具对象总数有 %d"%tool3.count)
# 2.输出工具对象的总数
print(Tool.count)
类方法和静态方法
类方法
1. 类属性就是针对类对象定义的属性
1. 使用赋值语句在class 下方定义类属性
2. 类属性可以标记与这个类相关的特征
2. 类方法就是针对类对象定义的方法
1. 在类方法的内部可以直接访问类属性或调用其他的类方法语法格式如下
@classmethod
def 类方法名(cls):
pass
类方法需要用到装饰器 @classmethod,告诉解释器这是一个类方法
类方法的第一个参数应该是cls
1. 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
2. 和实例对象的第一个参数self类似
3. 提示其他名称也行,不过习惯cls
通过类名,调用类方法,调用方法时不需要传递cls参数
在方法内部
1. 可以通过cls.访问类的属性
2. 也可以通过cls.调用其他的类方法
示例需求:
定义一个工具类
每个工具都有自己的name
需求-- 在类中封封装一个 show_tool_count 的类方法,输出使用当前这个类,创建的对象个数
classTool(object):
# 定义类属性,适用赋值语句,记录所有工具对象的数值
count=0
# 定义类方法使用@classmethod装饰器表示类方法
@classmethod
defshow_tool_count(cls):
print("当前工具总数 %d"%cls.count)
def__init__(self, name):
self.name=name
Tool.count+=1
# 1.创建实例对象
tool1=Tool("斧头")
tool2=Tool("锤子")
tool3=Tool("尺子")
# 2.使用类方法输出工具对象的总数
Tool.show_tool_count()
静态方法
在开发时,如果需要在类中封装一个方法,
1. 既不需要访问实例属性调用实例方法
2. 也不需要访问类属性调用类方法
可以把方法封装成一个静态方法
语法格式
@staticmethod
def 静态方法名():
pass
classDog(object):
# 静态方法的定义
@staticmethod
defrun():
# 不访问实例属性,不访问类属性
print("跑")
# 调用静态方法,不需要创建实例对象
Dog.run()
方法综合案例
需求:
1. 设计一个Game类
2. 属性:
定义一个类属性:top_score 记录游戏的历史最高分
定义一个实例属性 player_name 记录当前游戏玩家的名字
3. 方法
静态方法:show_help 显示游戏帮助信息
类方法:show_top_score 显示历史最高分
实例方法:start_game 开始当前玩家的游戏
4. 主程序步骤:
查看帮助信息
查看历史最高分(调用类方法)
创建游戏对象,开始游戏
classGame(object):
# 定义类属性记录游戏最高分
top_score=0
def__init__(self, player_name):
# 记录玩家姓名
self.player_name=player_name
# 定义静态方法,显示游戏帮助信息
@staticmethod
defshow_help():
print("帮助信息:打掉所有飞机")
print("-"*50)
# 定义类方法,显示历史最高分
@classmethod
defshow_top_score(cls):
print("历史最高分 %d"%cls.top_score)
# 定义实例方法,开始当前游戏
defstart_game(self):
print("玩家 %s 请开始游戏"%self.player_name)
# 1.提示帮助信息
Game.show_help()
# 2.显示历史最高分
Game.show_top_score()
# 3.创建游戏对象,并开始游戏
game=Game("小明")
game.start_game()
小结
1. 实例方法:需要访问到实例属性
实例方法内部可以使用类名访问类属性
2. 类方法:方法内部,只需要访问到类属性,可以定义为类方法(装饰器:@classmethod)
3. 静态方法:方法内部,既不访问类属性,也不访问实例属性
24.单例
目标
单例设计模式
new 方法
Python中的单例
1.单例设计模式
设计模式
1. 设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的 成熟的解决方案
2. 使用设计模式是为了可宠用代码,让代码更容易被他人理解,保证代码可靠性
单例设计模式
1. 目的:让创建的对象,在系统中只有唯一的一个实例
2. 每次执行类名() 返回的对象,内存地址是相同的
单例设计模式的应用场景
1. 音乐播放对象
2. 回收站对象
3. 打印机对象等
2.new方法
使用类名() 创建对象时,Python的解释器会优先调用new方法为对象分配空间
new 是由object基类提供的内置静态方法,主要作用有两个:
1. 在内存中为对象分配空间
2. 返回对象的应用
Python 的解释器获得对象的引用后,将引用作为第一个参数,传递给 init 方法
重写 new 方法的代码非常固定
重写 new 方法一定要 return super()._new__(cls)
# 1.__new__方法为对象分配内存空间
# 2.返回对象的引用
classMusicPlayer(object):
# 元组,字典,重写new方法,覆盖父类object的方法实现
def__new__(cls, *args, **kwargs):
# 1.创建对象时,new方法会被自动调用
print("创建对象,分配空间")
# 为对象分配空间,super().__new__(cls)是重新使用object的函数,静态方法
instance=super().__new__(cls)
# 3.返回对象的引用
returninstance
def__init__(self):
print("播放器初始化")
# 创建播放器对象
player=MusicPlayer()
print(player)
3.Python中的单例
单例:让类创建的对象,在系统中只有唯一的一个实例
1. 定义一个类属性,初始值是None,用于记录单例对象的引用
2. 重写_new__方法
3. 如果类属性 is None ,调用父类方法分配空间,并在类属性中记录结果4. 返回类属性中记录的对象引用
notion image
4.只执行一次初始化工作
在每次使用类名() 创建对象时,Python都会自动调用两个方法:
1. new方法分配内存空间
2. init方法进行对象初始化
python中的单例改造new方法后,每次得到第一次创建对象的引用
但是,初始化方法还会被再次调用
需求:
1. 让初始化方法只被执行一次
解决办法
1. 定义一个类属性 init_flag 标记是否执行过初始化动作,初始值为False 2. 在init 方法中判断init_flag 如果为false就执行初始化动作
3. 让后将 init_flag 方法设置为true
4. 这样再次调用初始化方法时,初始化动作就不会被再次执行了
# 单例得到的对象引用应该是一样的
classMusicPlayer(object):
# 创建类属性
instance=None
# 记录是否执行过初始化方法
init_flag=False
# 改造__new__方法
def__new__(cls, *args, **kwargs):
# 1.判断类属性是否是空对象,说明对象还未创建
ifcls.instanceisNone:
# 2.若对象未创建,调用父类方法为第一个对象分配空间
cls.instance=super().__new__(cls)
# 3.返回第一个对象的引用
returncls.instance
def__init__(self):
# 1.判断是否执行过初始化动作,执行过则返回
ifMusicPlayer.init_flag:
return
# 2.若没执行过执行初始化动作
print("初始化播放器")
# 3.修改类属性的标记
MusicPlayer.init_flag=True
# 1.创建多个对象
player1=MusicPlayer()
print(player1)
player2=MusicPlayer()
print(player2)
只执行一次初始化动作,及单例实现
执行结果:
初始化播放器
<__main__.MusicPlayerobjectat0x000001F65BBB2C70> <__main__.MusicPlayerobjectat0x000001F65BBB2C70>
25.异常
目标:
异常的概念
捕获异常
异常的传递
自定义异常
1.异常的概念
程序执行时,如果Python解释器遇到一个错误,会停止程序的执行,并且显示一些错误信息,这就是异常
程序停止执行并且提示错误信息这个动作,我们称之为:抛出异常
2.捕获异常
2.1简单的捕获异常语法
1. 在程序开发中,如果对某些代码的执行不确定是否正确,可以增加 Try 来捕获异常2. 捕获异常最简单的语法格式
try:
尝试执行的代码
expect:
出现错误的处理
try(尝试):下方编写要尝试的代码,不确定是否能够正常执行的代码
expect(如果不是):下方编写尝试失败的代码
演练:
notion image
2.2错误类型捕获
在程序执行时,可能会遇到不同的类型异常,并且需要针对不同类型的异常做出不同的响应,这个时候,就需要捕获错误类型了
语法格式如下
try:
# 尝试执行的代码
pass
except错误类型1:
# 针对错误类型1,相应的代码处理
pass
except (错误类型2, 错误类型3:
# 针对错误类型2和3,相应的代码处理
pass
exceptExceptionasresult:
print("未知错误 %s"%result)
当Python解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型
异常类型捕获演练--用户输入整数
需求:
提示用户输入整数
使用8 除以用户输入的整数并且输出
try:
# 提示用户输入整数
num=int(input("请输入整数:"))
# 使用8 除以用户输入的整数并且输出
res=8/num
print(res)
# 判断错误类型,保证程序不被中止运行
exceptValueError:
print("值异常错误")
exceptZeroDivisionError:
print("不能以0为被除数")
捕获未知错误
在开发时,要预判所有可能出现的错误,有难度
如果希望程序无论出现任何错误,都不希望因python解释器抛出异常而终止可以再增加一个except:
语法格式如下
exceptExceptionasresult:
print("未知错误%s"%result)
notion image
异常捕获的完整语法
完整的语法格式
try:
# 尝试执行的代码
pass
except错误类型1:
# 针对错误类型1,相应的代码处理
pass
except (错误类型2, 错误类型3):
# 针对错误类型2和3,相应的代码处理
pass
exceptExceptionasresult:
print("未知错误 %s"%result)
else:
# 没有异常才会执行的代码
finally:
# 无论是否异常都会执行的代码
print("无论是否一场都会执行代码")
notion image
3.异常的传递
异常的传递:当函数/方法执行出现异常,会将异常函数传递给函数/方法的调用一方如果传递到主程序,仍然没有做异常处理,程序才会被中止
提示:
1. 在开发中,可以在主函数中增加异常捕获
2. 在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中 3. 这样就不需要在代码中,增加大量的异常捕获,保证代码的整洁
需求:
1. 定义函数 demo1() 提示用户输入一个整数
2. 定义函数demo2() 调用demo1
3. 在主程序中调用函数demo2()
向上传递异常
defdemo1():
num=int(input("输入整数:"))
returnnum
# print(demo1())
defdemo2():
print(demo1())
# 利用异常的传递性,在主程序中捕获异常
try:
demo2()
exceptExceptionasresult:
print("未知异常:%s"%result)
notion image
4.抛出raise异常
1.应用场景
1. 在开发中,除了代码执行错误解释器会抛出异常外
2. 还可根据应用程序特有的业务需求主动抛出异常
示例:
1. 提示用户输入密码,若长度少于8,则抛出异常
2.抛出异常
Python中提供了一个Exception 异常类
在开发时,如果满足特定业务需求时,希望抛出异常可以:
1. 创建一个 Exception 的对象
2. 使用 raise 关键字抛出异常现象
需求
1. 定义input_password函数,提示用户输入密码
2. 如果用户输入密码长度<8,则抛出异常
3. 如果用户输入密码长度>=8,则返回输入的密码
definput_password():
# 1.提示用户输入密码
password=input("请输入密码:")
# 2. 判断:如果密码长度>=8则返回输入的密码
iflen(password) >=8:
returnpassword
# 3.如果<8,则抛出异常
# 1.创建异常对象,可以使用错误信息字符串作为参数
ex=Exception("密码长度不够")
# 2.主动抛出异常
raiseex
try:
print(input_password())
exceptExceptionasresult:
print(result)
模块
1.模块的概念
模块是python程序架构的一个核心概念
1. 每个以扩展名.py结尾的Python源代码文件都是一个模块
2. 模块名同样也是一个标识符,需要符合标识符的命名规则
3. 在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具4. 模块就好比工具包,想使用这个工具包中的工具,就先导入模块
1.2模块导入的两种方式
1.1模块的导入import
import 模块名1,模块名2
提示:在导入模块时,每个导入应该独占一行
如: import 模块1
import 模块2
导入之后
通过模块名. 的方式使用模块工具
notion image
给模块指定别名
import 模块名 as 别名(符合大驼峰命名法
1.2from...import导入
只希望从某一个模块中导入部分工具,可以使用该方式
import 是一次性导入模块中的所有工具,并且通过模块名/别名访问语法格式:
from 模块名 import 工具名
导入之后:
1. 不需要通过模块名.
2. 可以直接使用模块提供的工具 - 全局变量,类,函数
注意:
如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖先导入的函数from 模块名 import *
导入模块名的全部工具,而不需要使用模块名.但是不推荐,因为函数重名不会提示1.3模块的搜索顺序
Python的解释器在导入模块时
1. 搜索当前目录指定模块名的文件,如果有就直接导入
2. 如果没有就搜索系统目录
在开发时,给文件起名,不要和系统的模块文件重名
Python中的每一个模块都有一个内置属性_file__可以查看模块的完整路径
notion image
1.4原则--每一个文件都是可以被导入的
1. 一个独立的py文件都是一个独立的模块
2. 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍
实际开发场景
1. 在实际开发中,每一个模块都是独立开发的
2. 开发人员通常会在模块下方增加一些测试代码
仅在模块内使用,而被导入到其他文件中不需要执行
_name__属性
1. 该属性可以做到在测试模块的代码,只有在测试情况下被运行,而在被导入时不会被执行
1.name是python的一个内置属性,记录着一个字符串
2.如果是被其他文件导入的._name__就是模块名
3.如果是当前执行的程序 _name__ 就是 _main__
# 模块向外界提供全局变量,函数,类
# 注意:直接执行的代码不是向外界提供的工具
defsay_hello():
print("hello")
# 主函数对模块进行测试
defmain():
say_hello()
# 如果直接执行模块,永远都是__main__
if__name__=="__main__":
main()
# 测试模块的代码,没有任何缩进的代码在导入时都会被执行一遍 # 文件被导入时,能够直接被执行的代码不需要执行
print("小明开发的模块")
say_hello()
2.包
概念:
1. 包是一个包含多个模块的特殊目录
2. 目录下有一个特殊的文件_init__.py
3. 包名的命名方式和变量名一致,小写字母 + _
好处:使用 import 包名可以一次性导入包中的所有模块
案例演练:
1. 新建一个cy_message的包
2. 在目录下,新建两个文件send_message和receive_message 3. 在send_message文件中定义一个send函数
4. 在receive_message文件中定义一个receive函数
5. 在外部直接导入cy_message的包
_init__,py
1. 要在外界使用包中的模块,需要在 _init__.py 中指定对外界提供的模块列表
notion image
notion image
发布模块
如果希望自己开发的模块,分享给其他人,可以按照以下步骤操作1.制作发布压缩包步骤
步1:创建setup.py
1).setup.py 的文件
fromdistutils.coreimportsetup
setup(
name="cy_message", # 包名
version="1.0", # 版本名
description="cy的发送与接收信息的模块", # 描述信息
long_description="w完整的发送与接收信息的模块", # 完整描述信息 author="cy", # 作者名
author_email=" ", # 作者邮箱
url="", # 发布地址
py_modules=["cy_message.send_message",
"cy_message.receive_message"]
)
步2:构建模块
2). python3 setup.py build
步3:生成发布压缩包
3).python3 setup.py sdist
notion image
2.安装模块
tar -zxvf cy_message-1.0.tar.gz
sudo python setup.py install
3.卸载目录
直接删除安装模块的目录
pip安装第三方模块
pip install 模块名
pip uninstall 模块名
文件
目标:
1. 文件的概念
2. 文件的基本操作
3. 文件/文件夹的基本操作
4. 文本文件的编码方式
1.文件的概念
1.1文件的概念
1. 计算机的文件,就是存储在某种长期存储设备上的一段数据 2. 长期存储设备包括:硬盘,u盘,移动硬盘,光盘
文件的作用
将数据长期的保存下来,在需要的时候使用
1.2文件的存储方式
1. 在计算机中,文件是以二进制的方式保存在磁盘上的
文本文件和二进制文件
1. 文本文件
1). 可以使用文本编辑软件查看
2). 本质还是二进制文件
3). 例如 python的源文件
2. 二进制文件
1). 保存的内容不是给人直接阅读的,而是提供给其他软件使用的 2). 例如:图片文件,音频文件,视频文件等
3). 二进制文件不能使用文本编辑软件查看
2.文件的基本操作
2.1操作文件的套路
在计算机中操作文件的套路非常固定,一共具有三个步骤:
1. 打开文件
2. 读,写文件
3. 关闭文件
2.2操作文件的函数/基本方法
在python中要操作文件需要记住1个函数和3个方法
01 open打开文件、并且返回文件操作对象(函数
02 read 读取文件,将文件内容读取到内存
03 write 将指定内容写入文件
04 close 关闭文件
2.3read方法--读取文件
1. open函数的第一个参数是要打开的文件名(区分大小写)
1). 如果文件存在,在则返回文件操作对象,不存在则抛出异常
2. read方法可以一次性读入,并返回文件的所有内容
3. close则负责关闭文件
1. 如果忘记关闭文件则会造成系统资源消耗,而影响后续对文件的访问4. 注意:方法执行后,会把文件指针移动到文件末尾
# 1.打开文件
file_txt=open("./test_file/文本文件.txt")
# 2.读取文件
print(file_txt.read())
# 3.关闭文件
file_txt.close()
提示:在开发中,通常会先编写打开和关闭的代码,在编写中间针对文件的读/写操作
文件指针
1. 文件指针标记从哪个位置开始读取数据
2. 第一次打开文件时,通常文件指针会指向文件的开始位置
3. 当执行了read()方法后,文件指针会移动到读取内容的末尾
默认情况下会移动到末尾
notion image
2.4打开文件的方式
open函数函数默认以只读方式打开文件,并且返回文件对象
# 1.打开文件
# r(read):以只读方式打开文件
# w(write): 在存在文件中写入数据,文件中存在内容会被覆盖
# a(append):在存在文件中写入数据,默认追加在上条数据末尾,不覆盖原有数据# r+(w+,a+):以读写的方式打开文件,文件存在则会被覆盖,文件不存在则会创建file = open("./test_file/文本文件.txt", "a", encoding="utf-8")
# 2.写入文件
txt = file.write("\n写不了一点\n")
# 3.关闭文件
file.close()
2.5按行读取文件的内容
read方法或默认一次性读取文件的全部内容
如果文件太大,对内存的占用会非常严重
readline(按行)方法
1. 该方法可以一次读取一行内容
2. 方法执行后,会把文件指针移动到下一行,准备再次读取
读取大文件正确操作:
file=open("./test_file/文本文件.txt", encoding="utf-8") # 无限循环
whileTrue:
txt=file.readline()
# 判断是否读取到内容
ifnottxt:
break
print(txt)
file.close()
2.6文件读写案例--复制文件
目标
用代码的方式,来实现文件赋值过程
源文件——>目标文件
小文件复制
1. 打开一个已有文件,读取完整内容,并写入到另外一个文件
2. 关闭文件
# 1.打开文件
file=open("./test_file/文本文件.txt", "r", encoding="utf-8")
# 2.读取文件
txt=file.read()
print(txt)
# 3.复制全部内容写入到一个新的文件(以w+读写方式打开,以w只写方式打开)
# with open("./test_file/复制文件.txt", "w+", encoding="utf-8") as f: withopen("./test_file/复制文件1.txt", "w", encoding="utf-8") asf: # 3.写入文件,将txt作为参数
f.write(txt)
file.close()
notion image
大文件复制
1. 打开一个已有文件,读取完整内容,并顺序(readline)写入到另外一个文件2. 关闭文件
# 1.打开文件
file_read=open("./test_file/文本文件.txt", "r", encoding="utf-8") file_write=open("./test_file/复制大文件.txt", "w", encoding="utf-8")
# # 2.读写
whileTrue:
# 读取文件,一行一行的读取
txt=file_read.readline()
# 判断是否读取到内容
ifnottxt:
break
file_write.write(txt)
# 3.关闭
file_read.close()
file_write.close()
文件/目录的常用管理操作
在python中,想要实现对文件或者目录进行管理,需要导入os模块文件操作
1. rename 重命名文件 os.rename(原文件名,目标文件名)
2. remove 删除文件 os.remove(文件名)
importos
# 重命名
os.rename("./test_file/文本文件.txt", "./test_file/文本文件(rename).txt") # 删文件
os.rename("./test_file/文本文件(rename).txt")
# 目录列表os.dir(目录名)
dir=os.listdir(".")
print(dir)
# 创建目录mkdir()
os.mkdir("./test_file/mkdir.txt")
# 删除目录rmdir()
os.rmdir("./test_file/mkdir.txt")
文本文件的编码格式
文本文件存储的内容是基于字符编码的文件,常见的编码又ASCII编码,Unicode编码等python2.x默认使用ASCII编码
python3.x默认使用utf-8编码
ASCII编码和Unicode编码
ASCII
1. 计算机中只有 256 个ASCII字符
2. 一个 ASCII 在内存中占用 1个字节的空间
1. 8个0/1的排列组合方式一共有256种,2**8种
UTF-8编码格式
1. 计算机中使用1-6个字节来表示一个 UTF-8 字符,涵盖地球上几乎所有地区的文字 2. 大多数汉字会使用3个字节表示
3. UTF-8 是 UNICODE 编码的一种编码格式
python2.x中使用中文
在文件第一行加入:# - utf-8 -,或者加入 # coding=utf-8
eval函数
eval()函数十分强大----将字符串当成有效的表达式计算求值并返回计算结果
# 字符串
# print(eval("1+1"))
# eval计算器
input_str=input("请输入算术题:")
print(eval(input_str))
执行结果
请输入算术题:(1+2)*5
15
不要滥用eval
不能直接使用eval函数直接转换input的结果
如果用户输入例如:_import__('os').system('rm aaa')能够直接使用终端命令,操作成功则返回0,失败则1,防止误操作,所以减少用
项目实战----飞机大战
目标
1. 强化面向对象程序设计
2. 体验使用 pygame 模块进行游戏开发
实战步骤
1. pygame 快速体验
2. 飞机大战实战
确认模块----pygame
1. pygame是一个python模块,为电子游戏设计
2. 官网:
pygame快速入门
目标
1. 项目准备
2. 使用pygame创建图形窗口
3. 理解图像并实现图像绘制
4. 理解游戏循环游戏时钟
5. 理解精灵精灵组
1.项目准备
1. 新建飞机大战项目
2. 新建一个pygame入门.py文件
3. 导入游戏素材图片
游戏的第一印象
1. 把一些静止的图像绘制到游戏窗口中
2. 根据用户的交互或其他情况,移动这些图像,产生动画效果 3. 根据图像之间是否发生重叠,判断敌机是否被摧毁等其他情况01.创建pygame窗口
小节目标
1.游戏的初始化和退出
2.理解游戏中的坐标系
3.创建游戏主窗口
4.简单的游戏循环
| 可以将图片素材绘制到游戏的窗口上,开发游戏之前需要知道如何建立游戏窗口
1.1游戏的初始化
1. 要使用pygame 提供的所有功能之前,需要调用init方法进行初始化
2. 在游戏结束前需要调用quit方法
pygame.init() 导入并初始化所有pygame模块,使用其他模块之前,必须先调用init方法pygame.quit() 卸载所有pygame模块,在游戏结束之前调用
1.2理解游戏中的坐标系
坐标系
1. 原点在屏幕左上角(0,0)
2. X轴水平方向向右,逐渐增加
3. Y轴垂直方向向下,逐渐增加
在游戏中,所有可见的元素都是以矩形区域来描述位置的
要描述一个矩形区域有四个要素:(x,y)(width,heigth)
notion image
pygame专门提供了一个类 pygame.Rect 用于描述矩形区域
pygame.Rect(x, y, width, height) # 创建矩形对象
其中的size属性会返回一个元组
(width,height)
# 其中pygame.Rect是一个比较特殊的类,内部只是封装了一些数字计算
# 不执行pygame.init()方法同样能直接使用
案例演练
需求:
1. 定义一个hero_rect矩形描述英雄的位置和大小
2. 输出英雄的坐标原点(x,y)
3. 输出英雄的尺寸(width, height)
4. 其中size属性第一个值是宽度width,第二个值是高度height
notion image
1.3创建游戏主窗口
pygame专门提供了一个模块 pygame.display 用于创建,管理游戏窗口
pygame.display.set_mode() 初始化游戏显示窗口
pygame.display.update() 刷新屏幕内容显示,稍后使用
1. set_mode()方法:
notion image
参数
1. 表示元组及size中代表的width与height,
2. flags:指定屏幕的附加选项,例如是否全屏等,默认不需要传递3. depth: 表示颜色的位数,默认自动匹配
返回值
1. 暂时理解为游戏的屏幕游戏的元素,都需要被绘制到游戏的屏幕上
注意:必须使用变量记录 set_mode 方法的返回结果,因为:后续所有的图像绘制都基于该返回结果
# import time
importpygame
# 调用初始化方法,加载pygame的模块
pygame.init()
# set_mode方法,创建游戏窗口 480*700
windows=pygame.display.set_mode((480, 700))
# time.sleep(2)
# 游戏循环
whileTrue:
pass
# 卸载模块,释放空间
pygame.quit()
02.理解图像并实现图像绘制
在游戏中,能够看到的大多数游戏元素都是图像
1. 图像文件初始时保存在磁盘上的,如果需要使用,第一步就需要被加载到内存
要在屏幕上看到某一个图像内容,需要按照三个步骤:
1. 使用pygame.image.load()加载图像数据
2. 使用游戏屏幕对象,调用blit方法将图像绘制到指定位置
3. 调用pygame.display.update()方法更新整个屏幕的显示(注:要看到绘制结果,一定要使用该 方法
需求:
| 绘制背景图
1. 加载背景图像
2. 指定在屏幕位置
3. 更新屏幕显示
importtime
importpygame
# 初始化
pygame.init()
# 绘制屏幕(width,height) 480*700
windows=pygame.display.set_mode((480, 700))
# 绘制游戏背景
bg=pygame.image.load("../images/background.png")
# 绘制图像到屏幕指定位置(x,y)=(0,0)原点 blit方法
windows.blit(bg, (0, 0))
# 更新屏幕显示
pygame.display.update()
# 设置游戏循环
time.sleep(2)
# while True:
#
# pass
# 卸载模块
pygame.quit()
|| 绘制英雄图像
需求:
1. 加载me1.png创建英雄飞机
2. 将英雄飞机绘制在屏幕的(200, 500)位置
3. 调用屏幕更新显示飞机图像
hero=pygame.image.load("../images/me1.png") # 1.2英雄飞机windows.blit(hero, (180, 500)) # 2.2 绘制飞机到(200,500)位置pygame.display.update()
透明图像:
1. png格式的图像支持透明
2. 在绘制图象时,透明区域不会显示任何内容
3. 但是下方已有内容,则会通过透明区域显示出来
notion image
理解update()方法的作用
1. 可以在windows对象完成所有blit方法后,统一调用一次display.update方法
2. 同样可以在屏幕上看到最终绘制的结果
使用set_mode()创建的windows对象是一个内存中的屏幕数据对象
可以理解成油画的画布
其中windows.blit()方法可以在画布上绘制很多图像
如上图飞机与背景,这些背景可重叠,或者覆盖
display.update()会将画布的最终结果绘制在屏幕上,这样可以提高屏幕绘制效率,增加游戏的流畅度
# 初始化
pygame.init()
# 绘制屏幕(width,height) 480*700
windows=pygame.display.set_mode((480, 700))
# 绘制游戏背景
# 1. 加载图像数据
bg=pygame.image.load("../images/background.png") # 1.1游戏背景# 2. 绘制图像到屏幕指定位置(x,y)=(0,0)原点 blit方法
windows.blit(bg, (0, 0)) # 2.1 绘制背景到原点
# 绘制英雄飞机
hero=pygame.image.load("../images/me1.png") # 1.2英雄飞机windows.blit(hero, (180, 500)) # 2.2 绘制飞机到(200,500)位置
# 3. 更新屏幕显示
pygame.display.update()
03.理解游戏循环和游戏时钟
让飞机移动
1.1.游戏中的动画实现原理
游戏的动画效果,本质上是:快速的在屏幕上绘制图像
例如电影:将多张静止的电影胶片,连续快速的播放,产生连贯的视觉效果
一般电脑上每秒绘制60次,就能够达到非常连续高品质的动画效果
每秒绘制的结果被称为帧Frame
1.2游戏循环
意味着游戏的正式开始
游戏循环上方的部分称为:游戏初始化主要:设置游戏窗口,绘制图像初始位置,设置游戏时钟(clock = time.Clock() )
游戏循环:主要:设置刷新帧率(clock.tick(60)),检测用户交互(事件监听),更新所有图像位置,更新屏幕显示
游戏循环的作用
1. 保证游戏不会直接退出
2. 变换图像的位置 -- 动画效果
每隔1/60秒移动一下图像的位置
调用pygame.display.update()更新屏幕显示
3.检测用户交互 -- 鼠标,键盘等
1.3游戏时钟
1. pygame 专门提供了一个类python.time.Clock()可以非常方便的设置屏幕绘制速度 -- 刷新帧率
2. 要使用时针对象有两步
1)在游戏初始化创建一个时钟对象
2)在游戏循环内调用tick(帧率)方法
3. tick方法会根据上次被调用的时间,自动设置游戏循环中的延时
# 4. 创建时钟对象
clock=pygame.time.Clock()
# i = 0
# 设置游戏循环
# time.sleep(2)
whileTrue:
# 让游戏循环内部的代码在一分钟内重复执行60次,指定循环体内部代码执行的频率 clock.tick(1)
# print(i)
pass
1.4.英雄的简单动画实现
需求:
1. 在游戏初始化定义一个pygame.Rect的变量记录英雄的初始位置2. 在游戏循环中每次让英雄的 y-1 --向上移动(游戏坐标x,y轴) 3. y<=0 将英雄移动到屏幕的底部
提示:
1. 每一次调用update()方法之前,需要把所有的游戏图像都重新绘制一遍2. 而且应该最先绘制背景图像(遮挡原有的图像,达到流畅的动画效果)
# 5.定义一个 rect 变量记录飞机的初始位置
hero_rect=pygame.Rect(180, 500, 102, 126)
# 设置游戏循环
# time.sleep(2)
whileTrue:
# 让游戏循环内部的代码在一分钟内重复执行60次,指定循环体内部代码执行的频率 clock.tick(60)
# print(i)
# 5.1 修改飞机的位置
hero_rect.y-=1
# 判断飞机的 y轴的位置是否<=0即是否溢出屏幕
ifhero_rect.y<=0:
hero_rect.y=700
# 5.2 调用blit方法绘制图像
windows.blit(bg, (0, 0))
windows.blit(hero, hero_rect)
# 5.3 绘制完成用 update 更新显示
pygame.display.update()
作业:
1. 英雄向上飞行,当英雄完全飞出屏幕后
2. 将飞机移动到屏幕的底部
# 判断飞机的 y轴的位置是否<=0即是否溢出屏幕其中 bottom = Y + height # if hero_rect.y + hero_rect.height <= 0:
ifhero_rect.bottom<=0:
hero_rect.y=700
1.5.在游戏循环中监听事件
事件event
1. 就是游戏启动后,用户对游戏所做的操作
2. 例如:点击关闭按钮,点击鼠标,按下键盘
监听
1. 在游戏循环中,判断用户的具体操作
2. 只有捕获到用户的具体的操作,才能针对性的做出响应
代码实现
1. pygame 中通过pygame.event.get() 可以获取用户当前所做动作事件列表用户可以同一时间 做很多事情
提示:这段代码非常固定,几乎所有 pygame 游戏都大同小异
# 捕获事件
even_list=pygame.event.get()
iflen(even_list) >0:
print(even_list)
在游戏中捕获用户的具体操作,才能针对操作做出响应
# 监听事件(循环列表,时间是一个列表)
foreventinpygame.event.get():
# 判断用户是否按下关闭键,事件类型是否是退出事件
ifevent.type==pygame.QUIT:
print("退出游戏")
# 调用quit方法卸载模块,释放内存
pygame.quit()
# 退出系统,而不是退出循环,直接终止当前执行的程序
exit()
04.理解精灵和精灵组
1.1.精灵和精灵组
1. 在刚刚完成的案例中,图像加载,位置变化,绘制图像,都需要程序员编写代码分别处理
2. 为了简化开发步骤,pygame 提供了两个类
1. pygame.sprite.Sprite -- 存储图像数据 image和位置 rect 的对象2. pygame.sprite.Group
1.2.派生精灵子类
1. 新建 plane_sprites.py 文件
2. 定义GameSprite继承自 pygame.sprite.Sprite
注意:
1. 如果类的父类不是 object基类
2. 在重写初始化方法,一定要先super().一下父类的_init__方法
3. 保证父类中实现的 _init__方法代码能够被正常执行
属性
1. image精灵图像,使用image_name加载
2. rect 精灵大小,默认使用图像的大小
3. speed 精灵移动速度,默认为1
方法
1. update 每次更新屏幕时在游戏循环内调用
1. 让精灵的 self.rect.y += self.speed
提示
1. image 的get_rect()方法,可以返回pygame.Rect(0, 0, 图像宽,图像高)的对象
importpygame
classGameSprite(pygame.sprite.Sprite):
"""飞机大战游戏精灵"""
# 初始化方法
def__init__(self, image_name, speed=1):
# 调用父类的初始化方法,因为父类不是继承自object基类,避免出现继承的父类中的初始化方法重写了
super().__init__()
# 定义对象属性
self.image=pygame.image.load(image_name)
self.rect=self.image.get_rect()
self.speed=speed
defupdate(self):
# 在屏幕的垂直方向移动
self.rect.y+=self.speed
1.3.使用游戏精灵和精灵组创建敌机
需求:
1. 使用刚刚派生的游戏精灵精灵组创建敌机并实现敌机动画步骤:
1. 使用 from import 导入plane_sprite模块
2. 在游戏初始化创建精灵对象精灵组对象
3. 在游戏循环中精灵组分别调用update() 和 draw(windows)方法
职责
精灵
1. 封装image,位置rect,速度speed
2. 提供update() 方法,根据游戏需求,更新位置rect
精灵组
1. 包含多个精灵对象
2. update 方法,让精灵组中的所有精灵都调用update方法更新位置3. draw(windows) 方法,在windows 上绘制精灵组中的所有精灵
游戏框架搭建
目标:使用面向对象设计飞机大战游戏类
01.明确主程序职责
1. 一个游戏主程序的职责可以分为两部分:
1. 游戏初始化
2. 游戏循环
2. 根据明确的职责,设计PlaneGame类如下:
游戏初始化:
1. 设置游戏窗口
2. 设置游戏时钟
3. 创建精灵,精灵组
游戏循环:
1. 设置刷新帧率
2. 事件监听
3. 碰撞检测
4. 更新/绘制精灵组
5. 刷新屏幕显示
根据职责可封装私有方法,避免代码的冗长
02.实现飞机的大战的主游戏类
1. 明确文件职责
1. plane_main
1. 封装主游戏类
2. 创建游戏对象
3. 启动游戏
2. plane_sprites
1. 封装游戏中所有需要使用的英雄子类
2. 提供游戏的相关工具
代码实现:
新建plane_main.py文件,并且可执行
编写基础代码
使用常量代替固定的数值
1. 常量----不变化的量
2. 变量----可变化的量
应用场景
1. 在开发时,可能会需要使用固定的数值,例如屏幕的高度:700
2. 这时,建议不要直接使用固定数值,而应该使用常量
3. 开发时,为保证代码的可维护性,尽量不要使用魔法数字
常量的好处:
1.阅读代码时,通过常量名通过名字知道代替的意思
2.如果需要调整数值,只需要修改常量的定义就可以实现统一修改
在Python中并无真正意义的常量,类似于变量,只不过定义命名时全部大写,认为是固定值认为是常量,不轻易修改
游戏背景
目标:
1. 背景交替滚动的思路确定
2. 显示游戏背景
01.背景交替滚动的思路确定
1. 游戏启动后,背景会连续不断的向下方移动
2. 在视觉上产生英雄的飞机不断向上飞行的错觉
1. 游戏背景发生变化
2. 游戏主角位置不变
02.利用初始化方法,简化背景精灵的创建
1. 在主程序中,创建的两个精灵背景类,传入了相同的图像文件路径, 2. 创建第二个背景精灵时,在主程序中,设置背景精灵的图像位置精灵初始位置的设置,由精灵自己负责
1. 根据面向对象的原则:将对象的职责,封装到类的代码内部
2. 尽量简化程序调用一方的代码调用
# 飞机大战游戏背景派生精灵
classBackground(GameSprite):
def__init__(self, is_alt=False):
# 调用父类方法,传入图像参数
super().__init__("../images/background.png")
# 判断是否是交替图像
ifis_alt:
self.rect.y=-self.rect.height
敌机出场
目标:
1. 使用定时器添加敌机
2. 设计Enemy类
01. 使用定时器添加敌机
1. 游戏启动后,每隔 1秒会出现一架敌机
2. 每架敌机向屏幕西方飞行,飞行速度各不相同
3. 每家敌机出现的水平位置也不相同
4. 当敌机从屏幕下方飞出,则不会飞回屏幕
1.1定时器
pygame.time.set_timer(eventid, milliseconds)
1. 在每隔一段时间,执行一定动作
2. 使用 pygame.time.set_timer() 来添加定时器
3. set_timer 可以创建一个事件
4. 可以在游戏循环的事件监听方法中捕获该事件
5. 第一个参数事件代号需要基于常量:pygame.USEREVENT来指定6. 第二个参数时事件发生的毫秒值
定时器事件的监听
1. 通过 pygame.event.get() 获取当前时刻的所有事件
2. 遍历列表并且判断event.type 是否等于 eventid,相等则事件发生
02.设计enemy类
1. 游戏启动后,每隔 1秒会出现一架敌机(设置定时器)
2. 每架敌机向屏幕下方飞行飞行速度各不相同
3. 每家敌机出现的水平位置也不相同
4. 当敌机从屏幕下方飞出,则不会飞回屏幕(删除)
1.1.初始化方法
1. 指定敌机图片
2. 随机敌机的初始位置和初始速度
1.2 敌机类的准备
1. 加载敌机图片
2. 设置随机飞行速度
3. 设置随机生成位置
4. 判断是是否飞出屏幕
1.3创建敌机
1. 在 _create_sprite方法中,添加敌机精灵组
1. 敌机是定时被创建的因此在初始化方法中,不需要创建敌机
2. 在 _event_handler 中创建敌机,并添加到精灵组中
1. 调用精灵组的add方法,可以向精灵组中添加精灵
3. 在 _update_sprites ,让敌机调用update和draw方法
1.4移出屏幕销毁敌机
1. 敌机移出屏幕后,如果没有撞到英雄,敌机终结
2. 组要从敌机组删除,避免资源浪费
检测敌机被销毁
1. _del__内置方法在销毁前被自动调用
1. 使用精灵组中的kill方法
英雄出场
目标:
1. 设计英雄和子弹类
2. 使用pygame.key.get_pressed()移动英雄
3. 发射子弹
01设计英雄和子弹类
英雄需求
1. 游戏启动后,英雄出现在屏幕的水平中间位置,距离屏幕底部120像素() 2. 英雄每隔0.5s 发射一次子弹,每次连发三枚子弹(定时器
3. 英雄默认不会移动,需要通过左右方向键,控制英雄在水平方向移动子弹需求
1. 子弹从英雄的正上方发射,沿直线上方飞行
2. 飞出屏幕后,需要从精灵组中删除
02 移动英雄位置
在python中针对键盘按键的捕获,两种方式
1. 判断 event.type == pygame.KEYDOWN
# 方法1,一直按住方向键,不会捕获事件,要抬起才能继续捕获,降低操作灵活性
# elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
# # 是否按下右键
# print("向右移动")
2. 首先使用 pygame.key.get_pressed()返回所有按键元组,通过键盘常量,判断元素中某一个键 是否被按下如果被按下则数值为 1
# 使用键盘提供的方法获取键盘按键
keys_pressed=pygame.key.get_pressed()
# 判断元组中对应的索引值 1
ifkeys_pressed[pygame.K_RIGHT]:
print("向右移动")
03.移动英雄位置
1. 在hero类中重写 update方法
1. 用速度speed和英雄rect.x 进行叠加
2. 不需要调用父类方法
2. 在_event_handler中根据左右方向键设置英雄的速度
1. 向右:speed = 2
2. 向左:speed = -2
3. 其他:speed = 0
3. 控制英雄的运动边界
在 hero类中update判断英雄是否超出边界
right = x + width 利用right 属性可以针对右侧设置精灵位置
# 判断是否移出屏幕,不能移出
# 判断是否从左侧飞出 x = 0
ifself.rect.x<=SCREEN_RECT.x:
self.rect.x=0
# 判断是否从右侧飞出 right = x + width
ifself.rect.right>=SCREEN_RECT.right:
self.rect.right=SCREEN_RECT.right-SCREEN_RECT.x
04 发射子弹
1. 添加子弹发射事件
1. 定义定时器常量(eventid
2. 在初始化方法中,调用set_timer方法设置定时器事件
3. 在游戏循环中,监听定时器事件
2. 定义子弹类
1. 子弹从英雄正上方发射沿直线向上飞行
2. 飞出屏幕从精灵组中删除
3. bullet
1. 初始化方法
指定子弹图片
初始速度 = -2
2. 重写update方法
判断是否飞出屏幕,飞出则从精灵族中删除
4. 发射子弹
1. 在 hero 的初始化方法中,添加子弹精灵组属性
2. 添加update和draw方法
3. 实现fire方法
创建子弹精灵
设置初始位置 -- 英雄正上方
将子弹添加到精灵组
4. 一次发射三枚子弹
碰撞检测
01了解碰撞检测方法
pygame,spritegroupcollide()
1. 两个精灵组中的所有精灵的碰撞检测
1. groupcollide(group1, group2, dokill1, dokill2, collided) 2. 如果 dokill 设置为 true,则发生碰撞的精灵将自动被移除3. collided 参数是用于计算碰撞的回调函数
1. 如果没有指定,则每个精灵必须有一个 rect 属性
pygame.sprite.spritecollide()
1. 判断某个精灵和指定精灵组中的精灵碰撞
1. spritecollide(sprite, group, dokill, collided) => Sprite_list
拓展
1.创建敌机种类2
1. 创建敌机种类,设置速度为1
2. 设置敌机种类每5秒钟生成一架
3. 敌机能够发射子弹,碰撞到英雄,英雄死亡
vi -- 终端中的编辑器