Python基础知识(三)
Python基础知识(三)
(一)循环结构和选择结构
1. 条件表达式
绝大部分合法的Python表达式都可以作为条件表达式。
在选择和循环结构中,条件表达式的值只要不是False、0(或0.0、0 j等)、空值None、空列表、空元组、空集合、空字典、空字符串、空range对象或其他空迭代对象,Python解释器均认为与True等价。
>>> if 666: #使用整数作为条件表达式
print(9)
9
>>> a = [3, 2, 1]
>>> if a: #使用列表作为条件表达式
print(a)
[3, 2, 1]
>>> a = []
>>> if a: #空列表等价于False
print(a)
else:
print('empty')
empty
>>> i = s = 0
>>> while i <= 10: #使用关系表达式作为条件表达式
s += i
i += 1
>>> print(s)
55
>>> i = s = 0
>>> while True: #使用常量True作为条件表达式
s += i
i += 1
if i > 10: #符合特定条件时使用break语句退出循环
break
>>> print(s)
55
>>> s = 0
>>> for i in range(0, 11, 1): #遍历序列元素
s += i
>>> print(s)
55
(1)关系运算符
Python中的关系运算符可以连续使用,这样不仅可以减少代码量,也比较符合人类的思维方式。
>>> print(1<2<3) #等价于1<2 and 2<3
True
>>> print(1<2>3)
False
>>> print(1<3>2)
True
在Python语法中,条件表达式中不允许使用赋值运算符“=”,避免了误将关系运算符“==”写作赋值运算符“=”带来的麻烦。在条件表达式中使用赋值运算符“=”将抛出异常,提示语法错误。
>>> if a=3: #条件表达式中不允许使用赋值运算符
SyntaxError: invalid syntax
>>> if (a=3) and (b=4):
SyntaxError: invalid syntax
(2)逻辑运算符
逻辑运算符and、or、not分别表示逻辑与、逻辑或、逻辑非。对于and而言,必须两侧的表达式都等价于True,整个表达式才等价于True。
对于or而言,只要两侧的表达式中有一个等价于True,整个表达式就等价于True;对于not而言,如果后面的表达式等价于False,整个表达式就等价于True。
逻辑运算符and和or具有短路求值或惰性求值的特点,可能不会对所有表达式进行求值,而是只计算必须计算的表达式的值。
>>> 3 and 5 #整个表达式的值是最后一个计算的子表达式的值
5
>>> 3 or 5
3
>>> 0 and 5 #0等价于False
0
>>> 0 or 5
5
>>> not [1, 2, 3] #非空列表等价于True
False
>>> not {} #空字典等价于False
True
2. 选择结构
常见的选择结构有单分支选择结构、双分支选择结构、多分支选择结构以及嵌套的分支结构,也可以构造跳转表来实现类似的逻辑。
循环结构和异常处理结构中也可以带有“else”子句,可以看作是特殊形式的选择结构。
2.1 单分支选择结构
编写程序,输入使用空格分隔的两个整数,然后按升序输出。
x = input('Input two number:')
a, b = map(int, x.split())
if a > b:
a, b = b, a #序列解包,交换两个变量的值
print(a, b)
2.2 双分支选择结构
求解鸡兔同笼问题。
jitu, tui = map(int, input('请输入鸡兔总数和腿总数:').split())
tu = (tui - jitu*2) / 2
if int(tu) == tu:
print('鸡:{0},兔:{1}'.format(int(jitu-tu), int(tu)))
else:
print('数据不正确,无解')
Python还提供了一个三元运算符,并且在三元运算符构成的表达式中还可以嵌套三元运算符,可以实现与选择结构相似的效果。语法为
value1 if condition else value2
当条件表达式condition的值与True等价时,表达式的值为value1,否则表达式的值为value2。
>>> b = 6 if 5>13 else 9 #赋值运算符优先级非常低
>>> b
9
2.3 多分支选择结构
使用多分支选择结构将成绩从百分制变换到等级制。
def func(score):
if score > 100 or score < 0:
return 'wrong score.must between 0 and 100.'
elif score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
2.4 选择结构的嵌套
使用嵌套选择结构将成绩从百分制变换到等级制。
def func(score):
degree = 'DCBAAE'
if score > 100 or score < 0:
return 'wrong score.must between 0 and 100.'
else:
index = (score - 60) // 10
if index >= 0:
return degree[index]
else:
return degree[-1]
3. 循环结构
Python主要有for循环和while循环两种形式的循环结构,多个循环可以嵌套使用,并且还经常和选择结构嵌套使用来实现复杂的业务逻辑。
while循环一般用于循环次数难以提前确定的情况,当然也可以用于循环次数确定的情况;
for循环一般用于循环次数可以提前确定的情况,尤其适用于枚举或遍历序列或迭代对象中元素的场合。
对于带有else子句的循环结构,如果循环因为条件表达式不成立或序列遍历结束而自然结束时则执行else结构中的语句,如果循环是因为执行了break语句而导致循环提前结束则不会执行else中的语句。
两种循环结构的完整语法形式分别为:
while 条件表达式:
循环体
[else:
else子句代码块]
和
for 取值 in 序列或迭代对象:
循环体
[else:
else子句代码块]
3.1 for循环和while循环
编写程序,输出1~100之间能被7整除但不能同时被5整除的所有整数。
for i in range(1, 101):
if i%7==0 and i%5!=0:
print(i)
编写程序,打印九九乘法表。
for i in range(1, 10):
for j in range(1, i+1):
print('{0}*{1}={2}'.format(i,j,i*j), end=' ')
print() #打印空行
3.2 break和continue语句
一旦break语句被执行,将使得break语句所属层次的循环提前结束;
continue语句的作用是提前结束本次循环,忽略continue之后的所有语句,提前进入下一次循环。
编写程序,计算小于100的最大素数。
for n in range(100, 1, -1):
if n%2 == 0:
continue
for i in range(3, int(n**0.5)+1, 2):
if n%i == 0:
#结束内循环
break
else:
print(n)
#结束外循环
break
4. 综合案例解析
输入若干个成绩,求所有成绩的平均分。每输入一个成绩后询问是否继续输入下一个成绩,回答“yes”就继续输入下一个成绩,回答“no”就停止输入成绩。
numbers = []
while True:
x = input('请输入一个成绩:')
#异常处理结构,用来保证用户只能输入实数
try:
#先把x转换成实数,然后追加到列表numbers尾部
numbers.append(float(x))
except:
print('不是合法成绩')
#下面的循环用来限制用户只能输入任意大小写的“yes”或者“no”
while True:
flag = input('继续输入吗?(yes/no)').lower() #lower函数将字符串中所有的大写字母转化为小写字母
if flag not in ('yes', 'no'):
print('只能输入yes或no')
else:
break
if flag=='no':
break
#计算平均分
print(sum(numbers)/len(numbers))
编写程序,判断今天是今年的第几天。
import time
date = time.localtime() #获取当前日期时间
print(date)
year, month, day = date[:3]
day_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if year%400==0 or (year%4==0 and year%100!=0): #判断是否为闰年
day_month[1] = 29
if month == 1:
print(day)
else:
print(sum(day_month[:month-1])+day)
编写代码,输出由星号*组成的菱形图案,并且可以灵活控制图案的大小。
def main(n):
for i in range(n):
print((' * '*i).center(n*3))
for i in range(n, 0, -1):
print((' * '*i).center(n*3))
快速判断一个数是否为素数。
n = input("Input an integer:")
n = int(n)
if n in (2,3):
print('Yes')
#偶数必然不是素数
elif n%2 == 0:
print('No')
else:
#大于5的素数必然出现在6的倍数两侧
#因为6x+2、6x+3、6x+4肯定不是素数,假设x为大于1的自然数
m = n % 6
if m!=1 and m!=5:
print('No')
else:
for i in range(3, int(n**0.5)+1, 2):
if n%i == 0:
print('No')
break
else:
print('Yes')
编写程序,输入一个自然数n,然后计算并输出前n个自然数的阶乘之和1!+2!+3!+…+n!的值。
n = int(input('请输入一个自然数:'))
#使用result保存最终结果,t表示每一项
result, t = 1, 1
for i in range(2, n+1):
#在前一项的基础上得到当前项
t *= i
#把当前项加到最终结果上
result += t
print(result)
编写代码,模拟决赛现场最终成绩的计算过程。有至少3个评委,打分规则为删除最高分和最低分之后计算剩余分数的平均分。
while True:
try:
n = int(input('请输入评委人数:'))
if n <= 2:
print('评委人数太少,必须多于2个人。')
else:
break
except:
pass
scores = []
for i in range(n):
#这个while循环用来保证用户必须输入0到100之间的数字
while True:
try:
score = input('请输入第{0}个评委的分数:'.format(i+1))
#把字符串转换为实数
score = float(score)
assert 0<=score<=100
scores.append(score)
#如果数据合法,跳出while循环,继续输入下一个评委的分数
break
except:
print('分数错误')
#计算并删除最高分与最低分
highest = max(scores)
lowest = min(scores)
scores.remove(highest)
scores.remove(lowest)
finalScore = round(sum(scores)/len(scores),2) # round四舍五入到小数点后的n位
formatter = '去掉一个最高分{0}\n去掉一个最低分{1}\n最后得分{2}'
print(formatter.format(highest, lowest, finalScore))
(二)函数
1. 函数定义与调用基本语法
函数定义语法:
def 函数名([参数列表]):
'''注释'''
函数体
注意事项
函数形参不需要声明类型,也不需要指定函数返回值类型
即使该函数不需要接收任何参数,也必须保留一对空的圆括号
括号后面的冒号必不可少
函数体相对于def关键字必须保持一定的空格缩进
Python允许嵌套定义函数
编写生成斐波那契数列的函数并调用。
def fib(n):
a, b = 1, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
fib(1000)
2. 函数递归调用
函数的递归调用是函数调用的一种特殊情况,函数调用自己,自己再调用自己,自己再调用自己,…,当某个条件得到满足的时候就不再调用了,然后再一层一层地返回直到该函数第一次调用的位置。
使用递归法对整数进行因数分解。
from random import randint
def factors(num, fac=[]):
#每次都从2开始查找因数
for i in range(2, int(num**0.5)+1):
#找到一个因数
if num%i == 0:
fac.append(i)
#对商继续分解,重复这个过程
factors(num//i, fac)
#注意,这个break非常重要
break
else:
#不可分解了,自身也是个因数
fac.append(num)
facs = []
n = randint(2, 10**8)
factors(n, facs)
result = '*'.join(map(str, facs))
if n==eval(result):
print(n, '= '+result)
3. 函数参数
函数定义时圆括弧内是使用逗号分隔开的形参列表(parameters),函数可以有多个参数,也可以没有参数,但定义和调用时一对圆括弧必须要有,表示这是一个函数并且不接收参数。
调用函数时向其传递实参(arguments),根据不同的参数类型,将实参的引用传递给形参。
定义函数时不需要声明参数类型,解释器会根据实参的类型自动推断形参类型。
3.1 位置参数
位置参数(positional arguments)是比较常用的形式,调用函数时实参和形参的顺序必须严格一致,并且实参和形参的数量必须相同。
>>> def demo(a, b, c):
print(a, b, c)
>>> demo(3, 4, 5) #按位置传递参数
3 4 5
>>> demo(3, 5, 4)
3 5 4
>>> demo(1, 2, 3, 4) #实参与形参数量必须相同
TypeError: demo() takes 3 positional arguments but 4 were given
3.2 默认值参数
在调用带有默认值参数的函数时,可以不用为设置了默认值的形参进行传值,此时函数将会直接使用函数定义时设置的默认值,当然也可以通过显式赋值来替换其默认值。在调用函数时是否为默认值参数传递实参是可选的。
需要注意的是,在定义带有默认值参数的函数时,任何一个默认值参数右边都不能再出现没有默认值的普通位置参数,否则会提示语法错误。
带有默认值参数的函数定义语法如下:
def 函数名(……,形参名=默认值):
函数体
>>> def say( message, times =1 ):
print((message+' ') * times)
>>> say('hello')
hello
>>> say('hello', 3)
hello hello hello
3.3 关键参数
关键参数主要指调用函数时的参数传递方式,与函数定义无关。通过关键参数可以按参数名字传递值,明确指定哪个值传递给哪个参数,实参顺序可以和形参顺序不一致,但不影响参数值的传递结果,避免了用户需要牢记参数位置和顺序的麻烦,使得函数的调用和参数传递更加灵活方便。
>>> def demo(a, b, c=5):
print(a, b, c)
>>> demo(3, 7)
3 7 5
>>> demo(a=7, b=3, c=6)
7 3 6
>>> demo(c=8, a=9, b=0)
9 0 8
3.4 可变长度参数
可变长度参数主要有两种形式:在参数名前加1个*或2个**
*parameter用来接收多个位置参数并将其放在一个元组中
**parameter接收多个关键参数并存放到字典中
# *parameter的用法
>>> def demo(*p):
print(p)
>>> demo(1,2,3)
(1, 2, 3)
>>> demo(1,2)
(1, 2)
>>> demo(1,2,3,4,5,6,7)
(1, 2, 3, 4, 5, 6, 7)
# **parameter的用法
>>> def demo(**p):
for item in p.items():
print(item)
>>> demo(x=1,y=2,z=3)
('y', 2)
('x', 1)
('z', 3)
3.5 传递参数时的序列解包
传递参数时,可以通过在实参序列前加一个星号将其解包,然后传递给多个单变量形参。
>>> def demo(a, b, c):
print(a+b+c)
>>> seq = [1, 2, 3]
>>> demo(*seq)
6
>>> tup = (1, 2, 3)
>>> demo(*tup)
6
>>> dic = {1:'a', 2:'b', 3:'c'}
>>> demo(*dic)
6
>>> Set = {1, 2, 3}
>>> demo(*Set)
6
>>> demo(*dic.values())
abc
如果函数实参是字典,可以在前面加两个星号进行解包,等价于关键参数。
>>> def demo(a, b, c):
print(a+b+c)
>>> dic = {'a':1, 'b':2, 'c':3}
>>> demo(**dic)
6
>>> demo(a=1, b=2, c=3)
6
>>> demo(*dic.values())
6
4. 变量的作用域
变量起作用的代码范围称为变量的作用域,不同作用域内变量名可以相同,互不影响。
在函数内部定义的普通变量只在函数内部起作用,称为局部变量。当函数执行结束后,局部变量自动删除,不再可以使用。
局部变量的引用比全局变量速度快,应优先考虑使用。
全局变量可以通过关键字global来定义。
这分为两种情况:
一个变量已在函数外定义,如果在函数内需要为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内使用global将其声明为全局变量。
如果一个变量在函数外没有定义,在函数内部也可以直接将一个变量定义为全局变量,该函数执行后,将增加一个新的全局变量。
也可以这么理解:
在函数内只引用某个变量的值而没有为其赋新值,如果这样的操作可以执行,那么该变量为(隐式的)全局变量;
如果在函数内任意位置有为变量赋新值的操作,该变量即被认为是(隐式的)局部变量,除非在函数内显式地用关键字global进行声明。
>>> def demo():
global x
x = 3
y = 4
print(x,y)
>>> x = 5
>>> demo()
3 4
>>> x
3
>>> y
NameError: name 'y' is not defined
>>> del x
>>> x
NameError: name 'x' is not defined
>>> demo()
3 4
>>> x
3
>>> y
NameError: name 'y' is not defined
如果局部变量与全局变量具有相同的名字,那么该局部变量会在自己的作用域内隐藏同名的全局变量。
>>> def demo():
x = 3 #创建了局部变量,并自动隐藏了同名的全局变量
>>> x = 5
>>> x
5
>>> demo()
>>> x #函数执行不影响外面全局变量的值
5
5. Lambda表达式
lambda表达式可以用来声明匿名函数,也就是没有函数名字的临时使用的小函数,尤其适合需要一个函数作为另一个函数参数的场合。也可以定义具名函数。
lambda表达式只可以包含一个表达式,该表达式的计算结果可以看作是函数的返回值,不允许包含复合语句,但在表达式中可以调用其他函数。
>>> f = lambda x, y, z: x+y+z #可以给lambda表达式起名字
>>> f(1,2,3) #像函数一样调用
6
>>> g = lambda x, y=2, z=3: x+y+z #参数默认值
>>> g(1)
6
>>> g(2, z=4, y=5) #关键参数
11
>>> L = [1,2,3,4,5]
>>> print(list(map(lambda x: x+10, L))) #模拟向量运算
[11, 12, 13, 14, 15]
>>> L
[1, 2, 3, 4, 5]
>>> data = list(range(20)) #创建列表
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> import random
>>> random.shuffle(data) #打乱顺序
>>> data
[4, 3, 11, 13, 12, 15, 9, 2, 10, 6, 19, 18, 14, 8, 0, 7, 5, 17, 1, 16]
>>> data.sort(key=lambda x: x) #和不指定规则效果一样
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> data.sort(key=lambda x: len(str(x))) #按转换成字符串以后的长度排序
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> data.sort(key=lambda x: len(str(x)), reverse=True)
#降序排序
>>> data
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6. 生成器函数设计要点
包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数。
yield语句与return语句的作用相似,都是用来从函数中返回值。与return语句不同的是,return语句一旦执行会立刻结束函数的运行,而每次执行到yield语句并返回一个值之后会暂停或挂起后面代码的执行,下次通过生成器对象的__ next__()方法、内置函数next()、for循环遍历生成器对象元素或其他方式显式“索要”数据时恢复执行。
生成器具有惰性求值的特点,适合大数据处理。
# 编写并使用能够生成斐波那契数列的生成器函数。
>>> def f():
a, b = 1, 1 #序列解包,同时为多个元素赋值
while True:
yield a #暂停执行,需要时再产生一个新元素
a, b = b, a+b #序列解包,继续生成新元素
>>> a = f() #创建生成器对象
>>> for i in range(10): #斐波那契数列中前10个元素
print(a.__next__(), end=' ')
1 1 2 3 5 8 13 21 34 55
>>> for i in f(): #斐波那契数列中第一个大于100的元素
if i > 100:
print(i, end=' ')
break
144
>>> a = f() #创建生成器对象
>>> next(a) #使用内置函数next()获取生成器对象中的元素
1
>>> next(a) #每次索取新元素时,由yield语句生成
1
>>> a.__next__() #也可以调用生成器对象的__next__()方法
2
>>> a.__next__()
3
7. 精彩案例解析
编写函数,接收字符串参数,返回一个元组,其中第一个元素为大写字母个数,第二个元素为小写字母个数。
def demo(s):
result = [0, 0]
for ch in s:
if ch.islower():
result[1] += 1
elif ch.isupper():
result[0] += 1
return tuple(result)
编写函数,接收一个整数t为参数,打印杨辉三角前t行。
def yanghui(t):
print([1])
line = [1, 1]
print(line)
for i in range(2, t):
r = []
for j in range(0, len(line)-1):
r.append(line[j]+line[j+1])
line = [1]+r+[1]
print(line)
编写函数,接收一个正偶数为参数,输出两个素数,并且这两个素数之和等于原来的正偶数。如果存在多组符合条件的素数,则全部输出。
def demo(n):
def IsPrime(p):
if p == 2:
return True
if p%2 == 0:
return False
for i in range(3, int(p**0.5)+1, 2):
if p%i==0:
return False
return True
if isinstance(n, int) and n>0 and n%2==0:
for i in range(2, n//2+1):
if IsPrime(i) and IsPrime(n-i):
print(i, '+', n-i, '=', n)
编写函数,计算字符串匹配的准确率。
以打字练习程序为例,假设origin为原始内容,userInput为用户输入的内容,下面的代码用来测试用户输入的准确率。
def Rate(origin, userInput):
if not (isinstance(origin, str) and isinstance(userInput, str)):
print('The two parameters must be strings.')
return
right = sum((1 for o, u in zip(origin, userInput) if o==u))
return round(right/len(origin), 2)
编写函数模拟猜数游戏。系统随机产生一个数,玩家最多可以猜5次,系统会根据玩家的猜测进行提示,玩家则可以根据系统的提示对下一次的猜测进行适当调整。
from random import randint
def guess(maxValue=100, maxTimes=5):
#随机生成一个整数
value = randint(1,maxValue)
for i in range(maxTimes):
prompt = 'Start to GUESS:' if i==0 else 'Guess again:'
#使用异常处理结构,防止输入不是数字的情况
try:
x = int(input(prompt))
except:
print('Must input an integer between 1 and ', maxValue)
else:
#猜对了
if x == value:
print('Congratulations!')
break
elif x > value:
print('Too big')
else:
print('Too little')
else:
#次数用完还没猜对,游戏结束,提示正确答案
print('Game over. FAIL.')
print('The value is ', value)
guess()
汉诺塔问题基于递归算法的实现
def hannoi(num, src, dst, temp=None):
#声明用来记录移动次数的变量为全局变量
global times
#确认参数类型和范围
assert type(num) == int, 'num must be integer'
assert num > 0, 'num must > 0'
#只剩最后或只有一个盘子需要移动,这也是函数递归调用的结束条件
if num == 1:
print('The {0} Times move:{1}==>{2}'.format(times, src, dst))
times += 1
else:
#递归调用函数自身,
#先把除最后一个盘子之外的所有盘子移动到临时柱子上
hannuo(num-1, src, temp, dst)
#把最后一个盘子直接移动到目标柱子上
hannuo(1, src, dst)
#把除最后一个盘子之外的其他盘子从临时柱子上移动到目标柱子上
hannuo(num-1, temp, dst, src)
#用来记录移动次数的变量
times = 1
#A表示最初放置盘子的柱子,C是目标柱子,B是临时柱子
hannoi(3, 'A', 'C', 'B')
编写函数,使用算法实现冒泡排序算法
from random import randint
def bubbleSort(lst, reverse=False):
length = len(lst)
for i in range(0, length):
flag = False
for j in range(0, length-i-1):
#比较相邻两个元素大小,并根据需要进行交换,默认升序排序
exp = 'lst[j] > lst[j+1]'
#如果reverse=True则降序排序
if reverse:
exp = 'lst[j] < lst[j+1]'
if eval(exp):
lst[j], lst[j+1] = lst[j+1], lst[j]
#flag=True表示本次扫描发生过元素交换
flag = True
#如果一次扫描结束后,没有发生过元素交换,说明已经按序排列
if not flag:
break
编写函数,模拟选择法排序
def selectSort(lst, reverse=False):
length = len(lst)
for i in range(0, length):
#假设剩余元素中第一个最小或最大
m = i
#扫描剩余元素
for j in range(i+1, length):
#如果有更小或更大的,就记录下它的位置
exp = 'lst[j] < lst[m]'
if reverse:
exp = 'lst[j] > lst[m]'
if eval(exp):
m = j
#如果发现更小或更大的,就交换值
if m!=i:
lst[i], lst[m] = lst[m], lst[i]
编写函数,模拟二分法查找
def binarySearch(lst, value):
start = 0
end = len(lst)
while start < end:
#计算中间位置
middle = (start + end) // 2
#查找成功,返回元素对应的位置
if value == lst[middle]:
return middle
#在后面一半元素中继续查找
elif value > lst[middle]:
start = middle + 1
#在前面一半元素中继续查找
elif value < lst[middle]:
end = middle - 1
#查找不成功,返回False
return False