程序的控制结构
程序的控制结构有: 顺序结构,分支结构,循环结构
单分支结构 :可以理解为 如果xx 就yyy
以简单的猜数字为例子
(if True 代表始终为真, if not True 代表始终为假)
guess = eval(input())
if guess == 99:
print("Right!")
双分支:
guess = eval(input())
if guess == 99:
print("Right!")
eles:
print("False")
双分支的紧凑形式格式:
<表达式1> if <条件> else< 表达式2> >
:意思是如果条件成立,那么返回表达式1 ,否则,返回表达式2
表达式是语句的一部分,它不是语句
guess = eval(input())
print("猜{}了".format("对" if guess == 99 else "错"))
多分支结构
score = eval(input())
if score >= 90:
grade = "A"
elif score >= 80 :
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
print("成绩等级为{}".format(grade))
注意分支的设计,如果是 60,70,80 ,90 这样判断,那么91分会直接判定为D等级
条件判断符:> ,<, =, >=, <=, ==, !=
逻辑判断 : and,or ,not (简单来讲就是与或非)。 and <=> && , or <=> || ,not <=> !
guess = eval(input())
if guess > 99 or guess < 99:
print("Right")
else:
print("False")
异常处理
num = eval(input())
#平方操作
print(num**2)
如果用户没有输入整数怎么办?所以我们要对异常进行处理。
在语句块1 中发生except中指定的异常时,执行语句块2,当然也可以不指定,只要发生异常就执行语句块2
try: 语句块1 except<异常类型> : 语句块2
所以我们对程序进行改造
try:
num = eval(input())
print(num**2)
except NameError:
print("输入不是整数")
另一种异常处理
这个意思是,try中的语句块1,发生了异常,执行语句块2,不发生异常执行else 的语句块3,最后无论如何都要执行,finally的语句块4
练习:身体质量质数BMI
BMI计算:体重(kg)/ 身高的平方(m)
height,weight = eval(input("请输入身高(米)和体重(公斤)【逗号隔开】:"))
bmi = weight / (height**2)
print("BMI 的数值为:{:.2f}".format(bmi))
who,nat = "",""
if bmi < 18.5:
who,nat = "偏瘦","偏瘦"
elif 18.5 <= bmi < 24:
who ,nat = "正常","正常"
elif 24 <= bmi < 28:
who,nat = "偏胖","偏胖"
elif 28 <= bmi:
who,nat = "偏胖","肥胖"
print("BMI指标为:国内'{1}',国外'{0}'".format(who,nat))
只要设计多分支,一定要注意范围覆盖的问题!!!
循环
python提供了两种循环方式,遍历循环和无限循环
遍历循环,遍历某个结构形成的循环方式
for <循环变量> in <遍历结构>
<语句块>
常用:
计数循环
循环N次
for i in range(N)
<语句块>
#以M开始一直到N的前一个数字,步长为K
#range(0,5,2) 那么结果就是 0 2,4 ,K可以省略,默认为1
for i in range(M,N,K):
<语句块>
字符串遍历:
#遍历s字符串
#for c in s:
# <语句块>
for c in “python”:
print(c,end=",")
遍历列表:
#for item in ls:
# <语句块>
for item in ["py",1,2,3,"thon"]
print(item,end=",")
文件遍历:
#文件遍历循环
# fi 是文件标识符
#line 文件的每一行
for line in fi
<语句块>
无限循环
只要条件成立就执行表达式,执行之后再判断条件成立与否,不满足条件时,退出
while 条件:
<表达式>
循环控制保留字
break,continue
break,跳出当前循环
continue,跳过当前循环,执行下次循环
循环扩展
for <循环变量> in <遍历结构> :
<语句块1>
else :
<语句块2>
while <条件>:
<语句块1>
else:
<语句块2>
可以这么理解,else 就是正常完成循环的奖励!如果被break了,那么就不执行。
通过else 我们可以直接看出这个循环是否正常结束!
random库
random是使用随机数的Python的标准库
底层使用的是梅森选装算法生成的伪随机序列的元素。
基本随机函数: seed()
, random
扩展随机数函数:randint()
,getrandits()
,uniform()
,
randrange()
, choice()
, shuffle()
随机数种子,梅森选装算法根据随机数种子,产生随机数数序列。也就是说种子和随机序列是一对一的关系。
那么确定种子,就要使用seed(a = None)
函数,初始化给定的随机数种子,默认为当前系统时间。
random.seed(10)
: 当前的随机值种子为10。
如果要再现随机过程,那么就设定一个随机数,如果不需要,那么就使用默认的即可。
扩展随机数
randint(a,b)
: 生成一个【a,b】之间的随机整数
randrange(m,n,[k])
: 生成一个【m,n)以K为步长的随机整数。例如random.randrange(10,100,10) 产生【10,100)之间,以10为步长,取随机值。
getrandbits(k)
: 生成一个k比特长的随机整数,例如 random.getrandbits(8) 就会生成0-255之间的整数
uniform(a,b)
: 生成【a,b】之间的随机小数
choice(seq)
: 从序列seq 中随机选取一个元素。例如 random.choice([1,2,3,4,5,6,7]) 就是在这个列表中随机选取一个值。
shuffle(seq)
:将序列 seq 中元素随机排列,返回打乱后的序列。
圆周率的计算
1.近似公式计算
朴素方法计算
pi = 0
N = 1000
for k in range(N):
pi += 1/pow(16,k) * ( \
4/(8*k+1) - 2/(8*k+4) \
- 1/(8*k+5)-1/(8*k+6))
print("圆周率的值是:{}".format(pi))
2.工程学的撒点方式,蒙塔科罗方法
在正方形的内部随机撒点,当点的数量足够多,点的洒落方式足够随机,那么这个方式就是可取的。撒点之后,我们对点的位置进行观察,圆内部的点 / 园外的点, 就是圆周率
from random import random
import time
Darts = 1000 * 1000 # 撒一百万的点
hits = 0 # 命中点
start = time.perf_counter()
for i in range(1,Darts+1): #随机撒100w个点从【1,100w+1】
x, y = random(),random() #横纵坐标
#计算到圆心的距离
dist = pow(x**2+y**2,0.5)
if dist <= 1.0:
hits +=1
#以上都只是计算了半个圆,所以也要乘以4
pi = 4 * (hits / Darts)
print("圆周率的值是:{}".format(pi))
print("运行时间是:{:.5f}s".format(time.perf_counter()-start))
课后练习:四位玫瑰树
for i in range(1000,10000):
t = str(i)
if pow(eval(t[0]),4)+pow(eval(t[1]),4)+pow(eval(t[2]),4)+pow(eval(t[3]),4) == i:
print(i)
100以内素数和
def Isprime(n):
for i in range(2,n):
if n%i == 0:
return False
return True
res = 0;
for i in range(2,101):
if Isprime(i):
res += i
print(res)
函数和代码复用
函数的定义和使用
函数可以在Java中对应方法,理解起来非常简单。
定义一个函数
def 函数名(参数1,参数2...参数n):
函数体
return 返回值
例如:计算 n!
def fact(n):
res = 1;
for i in range(1,n+1):
res *= i
return res
如果要执行函数,必须要调用,调用直接使用函数名即可;
可选参数
python的函数特性,可选参数传递,可选参数一定要放在必选参数的后面!!
def 函数名(<非可选参数>,<可选参数>):
函数体
return 返回值
例如计算 n! // m
#这里m =1 意味着,如果传递了第二个参数,
#那么就使用我们传递的,如果没有,那么就直接使用默认的m=1
def fact(n , m=1):
s = 1
for i in range(1,n+1):
s*=i
return s //m
可变参数
当我们不确定具体的参数有几个的时候,在Java中提供的是fun(int …) 代表传递 多个int 类型的参数
在Python 中,使用 *变量名 来解决可变的参数问题
例如 计算 n!*b
#格式
def fact(n,*b):
s = 1
for i in range(1,n+1):
s*=i
#将接受的参数以列表的方式存储,所以我们需要遍历列表
for item in b:
s*=item
return s
def fact
参数传递的另一种方式,通过名字来赋值
一般来说,给函数传递参数的时候,都是按照参数的位置传递的,同时我们也可以使用参数的名称
#这里m =1 意味着,如果传递了第二个参数,
#那么就使用我们传递的,如果没有,那么就直接使用默认的m=1
def fact(n , m=1):
s = 1
for i in range(1,n+1):
s*=i
return s //m
# 调用时,直接使用位置
fact(10,5)
#使用名称传递
fact(m = 5 , n = 10)
返回值
对于无返回值的函数来说,可以使用return 来结束函数,也可以不使用,
并且,python中,返回值可以是 1 个 ,也可以是多个!
还是之前的代码,计算 n! // m, 但是此时我们不仅返回 n! // m ,并且把参数也一并返回,返回的就是一个元组,元组是一种组合数据类型,与列表相似。
def fact (n , m =1):
s = 1
for i in range(1,n+1):
s*=i
#用,隔开
return s//m,n,m
fact(10,5)
#结果-》(724760,10,5)
#直接将对应的参数返回给 a,b,c
a,b,c = fact(10,5)
局部变量和全局遍量
全局变量就是程序所使用的参数,局部变量就是函数内部所使用的参数。
使用规则1: 局部变量和全局变量是不同的变量,局部变量和全局变量可能重名,但是并不相同,函数调用完,局部变量就会被释放。
我们也可以用 global
关键字在函数内部使用全局变量
举个例子
#定义两个全局变量
n,s = 10,100
def fact(n):
#函数内部使用了s变量,与全局变量s 完全不同
s = 1
for i in range(1,n+1):
s* = i
return s
#这里使用的s 是全局变量
print(fact(n),s)
#如果在函数内部要使用外部的全局变量,就使用global关键字
#定义两个全局变量
n,s = 10,100
def fact(n):
#使用外部的全局变量
global s
for i in range(1,n+1):
s* = i
return s
#这里使用的s 是全局变量,但是已经被函数运算给修改了
print(fact(n),s)
规则2 : 局部变量为组合数据类型且没有被创建,它的名字还是一个全局变量,那么它就等同于全局变量
ls = ["F","f"]
def func(a):
#这个ls 是列表类型,但是在函数内部并没有声明和初始化
#但是调用的时候发现有一个全局变量就叫ls,所以这里的ls 就是在使用全局变量
ls.append(a)
return
func("C")
print(ls)
#结果:["F","f","C"]
ls = ["F","f"]
def func(a):
#初始化一个局部变量
ls = []
#这里使用的就是局部变量
ls.append(a)
return
func("C")
print(ls)
#结果:["F","f"]
lambda 函数
python语言特性,函数式编程,在Java中使用的是一个接口,这接口只能有一个抽象方法。
lambda是一种匿名函数,即没有名字的函数
使用 lambda
关键字进行定义,函数名是返回结果
定义格式:
函数名 = lambda 参数 : 表达式
||
等价于
||
def 函数名 (参数):
函数体
return 返回值
举个例子
f = lambda x,y : x + y
#意思是,有两个参数x和y,进行的运算是 x + y,把结果返回给 f
#当然也可以没有参数
f = lambda:"lambda函数"
#当调用f() 时,就会返回 "lambda函数" 字符串
print(f())
对于lambda要谨慎使用,他是特殊使用函数的一种方式,对于特定的计算,使用没有任何问题,但是常规的函数定义还是要用def。
练习:七段数码管绘制
不同灯管的亮灭,就可以表示数字。
效果图:
思路,先绘制一个数字的对应的数码管
再绘制一串数字,绘制对应的数码管
再获得当前的系统时间,绘制对应的数码管
import turtle
#绘制一条线
def drawLine(draw):
#如果传入的参数为真,那么就将画笔放下绘制
#否则,就将画笔抬起,不在画板上绘制
turtle.pendown()if draw else turtle.penup()
#每一个线段都长40,最后让他右转90度
turtle.fd(40)
turtle.right(90)
# 绘制一个数字
def drawDigit(digit):
#根据图片描述的线段,我们要判断第一个线段,那些数字需要用到,
#2,3,4,5,6,8,9的显示都要用到,所以对传入的digit数字进行判断
#如果是其中一个,那么就绘制这条线,否则就把画笔抬起,不绘制这条线
drawLine(True) if digit in [2, 3, 4, 5, 6, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 1, 3, 4, 5, 6, 7, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 2, 3, 5, 6, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 2, 6, 8] else drawLine(False)
#第四条线绘制完,回到原点,此时要绘制第5条线,需要让他转向
turtle.left(90)
drawLine(True) if digit in [0, 4, 5, 6, 8] else drawLine(False)
drawLine(True) if digit in [0, 2, 3, 5, 6, 7, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 1, 2, 3, 4, 7, 8, 9] else drawLine(False)
#最后将画笔的方向调整为标准的方向
turtle.left(180)
turtle.penup()
#为后续的数字做准备,每一个数字间隔20像素
turtle.fd(20)
#绘制日期
def drawDate(date):
#遍历每一个字符所对应的数字,去绘制先
for c in date:
drawDigit(eval(c))
#方法入口
def main():
#窗口大小为长800宽350,在屏幕的200,200像素点
turtle.setup(800,350,200,200)
turtle.penup()
#先将海龟调整到窗口的最左边开始绘制
turtle.fd(-300)
#海龟的宽度为5
turtle.pensize(5)
drawDate("20181010")
#让海归消失,
turtle.hideturtle()
turtle.done()
#调用 main 来绘制
main()
结果:
现在我们还差一步,直接给定系统时间去绘制。并且美化一下这个图。
我们然那个线和线之间有一些空白,我们可以写一个drawGap()函数
def drawGap():
#把画笔抬起,不要留下痕迹
turtle.penup()
turtle.fd(5)
在绘制线段的方法中fd之前和之后调用它,就可以做到间隔了。
好看一些了。
使用Time库获取系统时间,增加年月日的标记,让年月日的显示的颜色不同。
那么我们就需要改写一下drawDate()函数,我们将参数规定为: %Y-%m=%d+
意思是,遍历字符串时,获取日期的年2019这样的数字,我们就直接绘制,当遍历到 - 时,我们就绘制年, = 绘制月,+ 绘制天
全部代码:
#绘制一个小数点
def drawcomnt():
turtle.penup()
turtle.fd(20)
turtle.right(90)
turtle.fd(39)
turtle.pendown()
turtle.dot(1)
turtle.penup()
turtle.fd(-40)
turtle.left(90)
turtle.fd(20)
#绘制一条线
def drawGap():
#把画笔抬起,不要留下痕迹
turtle.penup()
turtle.fd(5)
def drawLine(draw):
drawGap()
#如果传入的参数为真,那么就将画笔放下绘制
#否则,就将画笔抬起,不在画板上绘制
turtle.pendown()if draw else turtle.penup()
#每一个线段都长40,最后让他右转90度
turtle.fd(40)
drawGap()
turtle.right(90)
# 绘制一个数字
def drawDigit(digit):
#根据图片描述的线段,我们要判断第一个线段,那些数字需要用到,
#2,3,4,5,6,8,9的显示都要用到,所以对传入的digit数字进行判断
#如果是其中一个,那么就绘制这条线,否则就把画笔抬起,不绘制这条线
drawLine(True) if digit in [2, 3, 4, 5, 6, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 1, 3, 4, 5, 6, 7, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 2, 3, 5, 6, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 2, 6, 8] else drawLine(False)
#第四条线绘制完,回到原点,此时要绘制第5条线,需要让他转向
turtle.left(90)
drawLine(True) if digit in [0, 4, 5, 6, 8] else drawLine(False)
drawLine(True) if digit in [0, 2, 3, 5, 6, 7, 8, 9] else drawLine(False)
drawLine(True) if digit in [0, 1, 2, 3, 4, 7, 8, 9] else drawLine(False)
#最后将画笔的方向调整为标准的方向
turtle.left(180)
turtle.penup()
#为后续的数字做准备,每一个数字间隔20像素
turtle.fd(20)
#绘制日期
def drawDate(date):
turtle.color("red")
#格式: %Y-%m=%d+
for c in date:
if c == "-":
#使用write 绘制字符串,font设置字体和大小
turtle.write('年',font=("Arial",20,"normal"))
turtle.pencolor("green")
#设置40像素的间隔
turtle.fd(40)
elif c == "=":
turtle.write("月",font =("Arial",20,"normal"))
turtle.pencolor("blue")
turtle.fd(40)
elif c == "+":
turtle.write("日",font=("Arial",20,"normal"))
else:
drawDigit(eval(c))
#方法入口
def main():
#将海龟隐藏
turtle.hideturtle()
# 修改绘图的速度
turtle.speed(0)
#窗口大小为长800宽350,在屏幕的200,200像素点
turtle.setup(800,350,200,200)
turtle.penup()
#先将海龟调整到窗口的最左边开始绘制
turtle.fd(-300)
#海龟的宽度为5
turtle.pensize(5)
#使用gmtime时间结构体,并且使用strptime进行格式化时间字符串
drawDate(time.strftime("%Y-%m=%d+",time.gmtime()))
#让海龟消失,
drawcomnt()
turtle.done()
#调用 main 来绘制
main()
代码复用与函数递归
代码复用实际上就是一种面向对象的设计思想,就是代码的封装与调用,将功能封装为模块。模块内部设计要足够聚合,也就是功能越单一越好;模块与模块之间,连接的越少越好;这就是我们常说的高聚合低耦合。
至于递归,我在之前的Java文章中,专门有递归的练习和专题,python中我们已经学会了函数的创建以及基础数据类型,那么学习递归是非常简单的。
递归以及练习
#简单的小例子,算n 的阶乘
def fact(n):
if n == 0 :
return 1
else :
return n* fact(n-1)
经典问题,汉诺塔
#操作数
count = 0
# n 个盘子,src:源柱子, dst 目标柱子, mid 中间借助的柱子
def hanoi(n,src,dst,mid):
if n == 1:
print("{}:{}->{}".format(1,src,dst))
count+=1
else :
#把n-1个盘子从src 移动到 mid 柱子上,借助dst柱子
hanoi(n-1,src,mid,dst)
print("{}:{}->{}".format(n,src,dst))
count+=1
#然后把n-1个盘子从mid柱子上移动到dst柱子上,借助src柱子
hanoi(n-1,mid,dst,src)
PyInstaller 库的使用
我们在未来的某一天,一定会有这样的需求,将编写的源代码,转换为无需源代码的可执行文件。这样这个文件无论在哪一个平台,无论它到底装了Python环境与否,都可以运行。
所以我们就需要将源代码打包为一个可执行文件,所以就需要PyInstaller库来帮助我们,使用PyInstaller将.py文件转换为各个平台的可执行格式,例如windows 就是 .exe文件
PyInstaller 是第三方库,我们需要自己手动安装。
直接使用cmd 进行安装 ,
输入 pip install pyinstaller
即可
安装之后,使用命令行切换到要打包的 py文件目录下
使用命令 : pyinstaller -F <文件名>
之后我们再打开目录,就会多了三个文件夹
build 和 pychache 可以删除,在 dist 目录中可以看到一个可执行文件
这个就是打包之后的文件
pyinstaller 常用的参数
推荐使用 -F
至于 - i 举个例子
(图标文件的名字为 xx.ico, py 文件名为 yy.py)
pyinstaller -i xx.ico -F yy.py
这样可以直接生成以 xx.ico 为图标 可执行文件
练习:科赫雪花小包裹
简单来说绘制一个雪花,将一个线段分为三等分,把中间这段去掉,替换为等边三角形的两条边。这就是一次变换,也叫一阶
然后我们再把这些等分的线段看成单独的一条线段,在进行划分。
这就是科赫曲线的实现过程。
先绘制科赫曲线:
import turtle
#size 线段长度,n为阶数
def koch(size,n):
if n == 0:
turtle.fd(size)
else:
#确定绘制的角度
#先是平的,再是突起,再凹陷,再平,角度是固定的
for angle in [0,60,-120,60]:
turtle.left(angle)
#绘制曲线为当前线段1/3 的 n-1阶曲线
koch(size/3,n-1)
def main():
turtle.setup(800,400)
turtle.penup()
turtle.goto(-300,-50)
turtle.down()
turtle.pensize(2)
koch(600,3)
turtle.hideturtle()
turtle.done()
main()
koch 就是核心,既然会了科赫曲线的绘制,那么就改变main 来绘制科赫雪花
def main():
turtle.setup(600,600)
#把海龟调整到合适的位置
turtle.penup()
turtle.goto(-200,100)
turtle.pendown()
#设置画笔的宽度
turtle.pensize(2)
level = 3
for i in range(3):
koch(400,level)
turtle.right(120)
#理解不了循环
#那么就可以想象一下一个雪花就是奇数条科赫曲线的连接
#每一个科赫曲线绘制完成后,要保证多个科赫曲线连城环,所以要保证内角
#所以说,我们也可以绘制5边的,那么内角就是120度,所以外角为60度,那么就right()
turtle.hideturtle()
turtle.done()
结果:
测验:
import random
def genpwd(length):
if length == 0 :
return 0
Min = 10**(length-1)
Max = (10 ** length) -1
return random.randint(Min,Max)
length = eval(input())
random.seed(17)
for i in range(3):
print(genpwd(length))
def prime(m):
for i in range(2,m-1):
if m % i == 0 : return False
return True
n = eval(input())
res = ""
start = int(n)
start = start+1 if start < n else start
count = 5
while count > 0:
if prime(start):
res += str(start) + ","
count-=1
start+=1
print(res[:-1])