哈喽,你如约而至,我很开心!
这一关,是我们的第2个项目实操关卡:学会用函数编写程序。
还记得项目1里那个文字版小游戏吗?在那之后,你又挑战了一个思维关卡和一个知识关卡。想必现在的你,对项目实操更胸有成竹了吧。
项目2的难度和项目1基本相似,不过从我们学完函数开始,我们就有能力做出一点有用的程序了——项目2将带大家做一个迷你的实用程序。
这次的项目实操,不仅是对基础知识的巩固,也是思维层面的练习。在我看来,每一个项目都是一次试炼,试的是对知识的掌握程度,炼的是解决问题的思路。
这次的项目实操流程,和项目1是一模一样的:
互联网公司有两种角色,产品经理、程序员。产品经理提开发需求给程序员,然后程序员开发出满足需求功能的程序。
今天我们来做个模拟,我们扮演“程序员”接到“产品经理”需求,并完成开发的过程。
看过了需求文档,产品经理又跟你嘱咐:
“敏捷开发”是互联网的常态。刚刚当“程序员”的第一天,你就接到了这么急的需求,那该怎么办呢?我们来分析一下。
我们的任务是制作一个工作量计算器,虽然产品经理的需求很急,但我们不要着急开发,应该先梳理清楚这是一个什么需求,要做出的程序(产品)功能如何。毕竟,磨刀不误砍柴工。
既然是个计算器程序,那就是要输入信息后能计算出结果。为了搞清楚计算过程,我们需要根据案例倒推出计算公式。
我们先梳理一下需求文档中的关键信息:
参考代码:(这个公式粗略看来没有太大问题,不过后续编程时还需要打磨一下)
# 工时计算公式
size = 1.5
number = 2
time = size * 80 / number
# 人力计算公式
size = 0.5
time = 20.0
number = size * 80 / time
我们已经搞清楚了核心计算过程,接下来我们得拆分一下阶段版本。
因为产品经理提到需求非常急,要赶快做出能用的程序,后续再迭代改良。所以,我们把程序版本大致规划成三个阶段:
明确了每一阶段的任务后,接下来就是好戏开场,让我们开始逐步用代码实现功能!
让我们先一起来攻克版本1.0:
要做一个“能用就好”的最基本的程序,我们可以直接编写一个带参数函数完成计算功能。程序写出来大概长这个结构:
# 工时计算
def estimated_time(size,number):
……(计算过程)
# 人力计算
def estimated_number(size,time):
……(计算过程)
estimated_time(参数1,参数2)
estimated_number(参数1,参数2)
也就是说,把计算过程写好,封装到函数里,然后需要计算的时候直接调用函数,然后运行程序就能完成计算。
我们刚才总结的计算公式是这样的:
# 工时计算公式
time = size * 80 / number
# 人力计算公式
number = size * 80 / time
现在请你来完善一下代码(注意,在函数中不但要完成计算,还需要用print语句把计算结果展示出来哦):
完善代码的过程中,我们会用到之前尾提到的知识点:格式化字符串。
参考代码:
# 无需修改代码,直接运行即可
# 工时计算
def estimated_time(size,number):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 人力计算
def estimated_number(size,time):
number = size * 80 / time
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 调用工时计算函数
estimated_time(1.5,2)
# 调用人力计算函数
estimated_number(0.5,20)
注:%f的意思是格式化字符串为浮点型,%.1f的意思是格式化字符串为浮点型,并保留1位小数。
运行结果:
项目大小为1.5个标准项目,使用2个人力完成,则需要工时数量为:60.0个
项目大小为0.5个标准项目,如果需要在20.0个工时完成,则需要人力数量为:2人
这里,“工作量计算器”的最基本版本就制作完成了,你把它交付给了产品经理。
产品经理拿走了版本1的代码,过了几天,他又回来跟你反馈计算公式有点问题:
产品经理把运行结果展示给你看:
那问题来了,要怎么调整代码,才能实现产品经理的需求对人数向上取整呢?也就是计算结果是1.5人的时候,取整数2,计算结果3.8人的时候,取整数4,计算结果10.1人的时候,取整数11……
在编程学习的两大瓶颈我们提到,写代码时碰到问题是正常的。它是一个“执行→遇到问题→解决问题→继续执行”的循环,不过相信这个循环总有break的时刻——因为问题终究会被解决!
所以在这里,也请你思考一下怎么独自解决这个问题。你即可以上网搜索新知识,也可以思考已有知识能否解决这个问题。如果做不出也没关系。
参考代码:
import math
# 人力计算
def estimated_number(size,time):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 调用人力计算函数
estimated_number(1,60)
第一步定位问题我们已经完成了:代码的计算过程应该对对人数向上取整。也就是计算结果是1.5人的时候,取整数2,计算结果3.8人的时候,取整数4,计算结果10.1人的时候,取整数11……
第二步,我们可以直接寻找新知识,在搜索引擎中搜索“python 取整”,就能找到向上取整的函数。
接下来的三到五步很简单,代码很快就出来了,请你运行体验一下:
import math
# 人力计算
def estimated_number(size,time):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 调用人力计算函数
estimated_number(1,60)
运行结果:
项目大小为1.0个标准项目,如果需要在60.0个工时完成,则需要人力数量为:2人
其中import math是因为使用ceil()函数需要导入math模块,就像第7关小游戏项目中,使用randint()函数(随机整数)需要导入random模块。我们会在第15关详细讲解关于“模块”的来龙去脉,现在你只需要读懂代码含义即可。
这个问题的解决还有另一种方式,在第二步的时候,你也可以运用已有知识解决。
有一个计算符不太常用,不知道你是否还记得:可以用%做取余数运算,比如print(5%4)的结果是1(5除以4余数为1)。
如果你想起了这个知识,那么在第三步,我们可以找到一个切入点:如果人数不是整数(余数不为零),就把人数用int()函数转化为整数,然后再加1。
照这个思路,写出的代码是这个样子:
import math
# 人力计算
def estimated_number(size,time):
if (size * 80 % time) != 0:
number = int(size * 80 / time) + 1
else:
number = size * 80 / time
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 调用人力计算函数
estimated_number(1,60)
在这里,用第一种方法代码更简洁。所以我们采用方法1,把之前的代码汇总到一起:
import math
# 工时计算
def estimated_time(size,number):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 人力计算
def estimated_number(size,time):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 调用工时计算函数
estimated_time(1.5,2)
# 调用人力计算函数
estimated_number(1,60)
#def estimated(size=0,time=1,number=1):
# size=input('请输入项目规格大小(需要计算可不输入):')
# time=input('请输入项目完工时间(工时)(需要计算可不输入):')
# number=input('请输入项目可用的人员数量(需要计算可不输入):')
# time=80*size/number
# number=80*size/time
# print('规模为{}的项目,{}个人来做,需要{}个工时可以完成'.format(size,number,time))
# return size,time,number
#estimated(1.5,2)
看完了两种解题思路后,请你在这停留片刻。想一想:你在写代码时碰到问题的第一反应是什么?
非常棒,思考已有知识是否可以解决问题亦或是上搜索引擎查寻找答案都是非常不错的选择,并不是直接问会的同事。
如果直接问已经会了的同学,这样做相当于你放弃了一次机会,一次可以独立解决问题的机会。同时,也放弃了独立解决问题可以带来的能力上的提升和成功后的愉悦。
所以,希望你开始学会克制,克制提问的冲动,把握解决问题的机会,让每一个问题都成为你成长路上坚实的阶梯。就算是同样从助教那得到帮助,先尝试自己去解决问题的人肯定也能从帮助中更好地理解和吸收。
好,回到项目:解决了计算问题后,产品经理又过来找你,这次他的需求是期望你简化代码:
我们继续用解决问题的流程来处理这个问题。
# 工作量计算函数
def estimated(参数……):
……
# 调用工作量计算函数
estimated(参数……)
要想实现这样的效果,我们需要解决一个问题:应该怎么传递参数,才能让函数estimated(参数……)自动区分并完成工时计算和人力计算?
同学们可以思考一下如何用学过的知识解决这个问题。
我们虽然需要用到参数传递的知识,但只靠参数传递并不能区分两种不同的计算方式,所以条件判断语句更为重要。
既然要用到条件判断语句,我们可以继续完善一下预期的代码结构:
# 工作量计算函数
def estimated(参数……):
if 条件1:
……(人力计算)
elif 条件2:
……(工时计算)
# 调用工作量计算函数
estimated(参数……)
所以,现在的问题又推进了一步:该如何设置条件,让条件1代表人力计算,条件2代表工时计算?
这个问题有多种解法,关键点是利用参数设置条件。我先跟你演示一种:(请留意代码注释)
import math
# 为函数设置了三个参数,并都带有默认参数)
def estimated(size=1,number=None,time=None):
# 人力计算:如果参数中填了时间,没填人数,就计算人力
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 工时计算:如果参数中填了人数,没填时间,就计算工时
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 调用函数的时候,传递两个参数,会自动计算出第三个参数
estimated(size=1.5,number=2)
estimated(size=0.5,time=20.0)
这里的代码用到了第9关默认参数的相关知识。在调用函数的时候,我们可以给指定的参数赋值,那剩余的参数就会是默认值(也就是在定义函数的那行定义了他们的默认值)。比如estimated(size=1.5,time=20.0),给size和time赋值,那剩下的number就默认为None。
你来运行体验一下:
import math
# 为函数设置了三个参数,并都带有默认参数
def estimated(size=1,number=None,time=None):
# 人力计算:如果参数中填了时间,没填人数,就计算人力
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 工时计算:如果参数中填了人数,没填时间,就计算工时
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 调用函数的时候,传递两个参数,会自动计算出第三个参数
estimated(size=1.5,number=2)
estimated(size=0.5,time=20.0)
运行结果:
目大小为1.5个标准项目,使用2个人力完成,则需要工时数量为:60.0个
项目大小为0.5个标准项目,如果需要在20.0个工时完成,则需要人力数量为:2人
我来考考你。调用函数estimated(time=70.0),输出的结果将是多少?
调用函数estimated(time=70.0),将默认size=1 , number=None,因为参数中填了时间没填人数(number=None),就计算人数。计算人数的时候,向上取整,所以答案是2.
刚才提到“合并成一个函数”这个问题不止一种解法。比如说,我们还可以这样设置三个参数来实现相同的效果:
感兴趣的同学可以练习一下,将代码修改成刚才提到的第二种解决方案:
参考代码:
import math
def estimated(types,size,other):
# 人力计算
if types == 1:
number = math.ceil(size * 80 / other)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,other,number))
# 工时计算
elif types == 2:
time = size * 80 / other
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,other,time))
estimated(1, 1.5, 2)
# 结果:项目大小为1.5个标准项目,如果需要在2.0个工时完成,则需要人力数量为:60人
estimated(2, 1.5, 2)
# 结果:项目大小为1.5个标准项目,使用2个人力完成,则需要工时数量为:60.0个
你把“稍作改良”的代码交给了产品经理。不过,这事还没完。
想让程序可以交互,显然要用input和print语句,这些都是我们曾经学过的。
为了方便,先把一些你可能需要复制的素材给你:
import math
def estimated(size=1,number=None,time=None):
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
'请选择计算类型:(1-人力计算,2-工时计算)'
'请输入项目大小:(1代表标准大小,可以输入小数)'
'请输入人力数量:(请输入整数)'
'请输入工时数量:(请输入小数)'
很棒,请开始你的表演吧!只要程序运行效果符合样例演示效果即可。
参考代码在这里:
import math
def estimated(size=1,number=None,time=None):
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
choice = input('请选择计算类型:(1-人力计算,2-工时计算)')
if choice == '1':
size = float(input('请输入项目大小:(1代表标准大小,可以输入小数)'))
number = None
time = float(input('请输入工时数量:(可以输入小数)'))
estimated(size,number,time)
elif choice == '2':
size = float(input('请输入项目大小:(1代表标准大小,可以输入小数)'))
number = int(input('请输入人力数量:(请输入整数)'))
time = None
estimated(size,number,time)
到这里,程序基本已经完成了。不过,为了展示用函数封装代码的精髓,我想再问大家一个问题:
如果要优化上面代码的结构的话,要怎么优化?先想想,看能不能回想起来,上个关卡我们学过的。
答案是:可以创建一个主函数,用来调用几个子函数。一起来温习一下上个关卡的代码:
def div(num1, num2):
growth = (num1 - num2) / num2
percent = str(growth * 100) + '%'
return percent
def warning():
print('Error: 你确定上个月一毛钱都不赚不亏吗?')
def main():
while True:
num1 = float(input('请输入本月所获利润'))
num2 = float(input('请输入上月所获利润'))
if num2 == 0:
warning()
continue
else:
print('本月的利润增长率:' + div(num1,num2))
break
main()
用图片来表示的话,是这样的:
如图所示,我们可以把每个独立的功能封装到每个单独的函数中,然后用一个主函数打包这些单独的函数,最后再调用主函数。
改造好了吧?看下参考代码:
import math
# 采集信息的函数
def myinput():
choice = input('请选择计算类型:(1-人力计算,2-工时计算)')
if choice == '1':
size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
number = None
time = float(input('请输入工时数量:(请输入小数)'))
return size,number,time
# 这里返回的是一个元组
elif choice == '2':
size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
number = int(input('请输入人力数量:(请输入整数)'))
time = None
return size,number,time
# 这里返回的数据是一个元组
# 完成计算的函数
def estimated(my_input):
# 把元组中的数据取出来
size = my_input[0]
number = my_input[1]
time = my_input[2]
# 人力计算
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 工时计算
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 主函数
def main():
my_input = myinput()
estimated(my_input)
# 调用主函数
main()
在这里,myinput()函数负责跟用户采集信息,estimated()函数负责完成计算,而main()函数把其他两个函数打包放在一起并传递了参数。所以只要调用main()函数就能让整个程序跑起来。你可以再次运行体验一下。
之所以写成“子函数+主函数”的代码结构,也是因为每个不同的功能封装在单独的函数代码中,方便后续修改、增删。
比如我们想要加一个功能“让程序循环运行,直到用户选择结束”。那么,就可以在程序中加上一个again函数。
你可以先试试,看是否能够自己完成这个函数的添加(注:可以复制到本地改造,完成后再复制上来)。
提示:1.需要新增变量和改造主函数;2.用到的知识是判断和循环;3.对代码进行调整是正常的(即不要期待总能一次成功)。
完成了吗?看下参考代码:
import math
# 变量key代表循环运行程序的开关
key = 1
# 采集信息的函数
def myinput():
choice = input('请选择计算类型:(1-工时计算,2-人力计算)')
if choice == '1':
size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
number = int(input('请输入人力数量:(请输入整数)'))
time = None
return size,number,time
# 这里返回的数据是一个元组
if choice == '2':
size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
number = None
time = float(input('请输入工时数量:(请输入小数)'))
return size,number,time
# 这里返回的是一个元组
# 完成计算的函数
def estimated(my_input):
# 把元组中的数据取出来
size = my_input[0]
number = my_input[1]
time = my_input[2]
# 人力计算
if (number == None) and (time != None):
number = math.ceil(size * 80 / time)
print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))
# 工时计算
elif (number != None) and (time == None):
time = size * 80 / number
print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))
# 询问是否继续的函数
def again():
# 声明全局变量key,以便修改该变量
global key
a = input('是否继续计算?继续请输入y,输入其他键将结束程序。')
if a != 'y':
# 如果用户不输入'y',则把key赋值为0
key = 0
# 主函数
def main():
print('欢迎使用工作量计算小程序!')
while key == 1:
my_input = myinput()
estimated(my_input)
again()
print('感谢使用工作量计算小程序!')
main()
你可以运行体验一下。
到这里,这一关就要收尾了。在本关我们模拟了“程序员”接“产品经理”需求的过程,开发出了一个迷你产品“工作量计算器”。同时,我们也对函数知识、解决问题的知识做了复习。最后,还给大家展示了“子函数+主函数”的编程结构。
不过,随着我们编写的代码规模越大,越容易犯错。编程中的报错(俗称bug)让人最为恼火,特别是当程序运行不通过而你又定位不到问题的时候。
下一关,我会给你讲如何调试(debug,即解决bug)的技巧,让头发少掉,让痛苦更轻,也更快过去。
下一关见!