欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从零开始学习PYTHON3讲义(四)让程序更友好

程序员文章站 2022-06-09 15:52:58
《从零开始PYTHON3》第四讲 先看看上一讲的练习答案。 程序完成的是功能,功能来自于“程序需求”(“需求”这个词忘记了什么意思的去复习一下第二讲)。 练习的程序需求当然就是练习题本身。所以编程类的练习题通常并没有所谓标准答案,只要能完成功能,都应当是正确的。下面是一个参考: 程序的执行结果是: ......

从零开始学习PYTHON3讲义(四)让程序更友好

《从零开始python3》第四讲

先看看上一讲的练习答案。
程序完成的是功能,功能来自于“程序需求”(“需求”这个词忘记了什么意思的去复习一下第二讲)。
练习的程序需求当然就是练习题本身。所以编程类的练习题通常并没有所谓标准答案,只要能完成功能,都应当是正确的。下面是一个参考:

def speedxy(t):
    y=((12*4.5-t)/(4.5*5/3-2.5))
    x=((t-5*y)/3)
    return x,y

x,y = speedxy(36)
print(x,y)
x,y = speedxy(48)
print(x,y)

程序的执行结果是:

6.0 3.6
14.0 1.2

讲到这里先要问一个问题,有人记得练习题的内容和上面执行的结果代表什么含义吗?

我曾经在一个项目组问过类似的问题,代码完成后时隔仅仅1周,能正确回答的,不超过30%。

我这里不是想说什么“人的记忆总是靠不住”之类的哲学问题。我想说的是,我们的程序,需要写的足够友好。至少过了很久我们拿起来,都能很容易的明白这是一个什么程序,用途是什么。


让程序更友好

让程序更友好这个概念包含至少两层意思:

  • 对自己更友好
  • 对用户更友好

我们先看如何对自己更友好。对自己更友好首先就是帮助编程人员自己和合作者理解程序的含义、功能,便于将来的代码重新使用,和便于错误的排查。

比如程序一开始,首先应当对这个程序的背景、需求、开发时间、程序作者等内容作出注释。甚至如果程序用于团队之外,可能还要附加作者的联系方式。让别人有问题的时候,方便得到你的帮助。我们继续用上一讲的练习来做一个例子:

"""
程序名:函数式编程解应用题练习
作者:andrew
时间:2018年6月1日

原题:
甲、乙两人相距36千米,相向而行.
如果甲比乙先走2小时,那么他们在乙出发2.5小时后相遇;
如果乙比甲先走2小时,那么他们在甲出发3小时后相遇;
甲、乙两人每小时各走多少千米?(假设甲乙的速度均匀稳定)
解题思路:
假设甲的速度为x千米/小时
假设乙的速度为y千米/小时
列方程组:
(2.5+2)x+2.5y=36
3x+(3+2)y=36 
根据方程2推导得出:
x=(36-5y)/3
代入方程1得出:
y=(12*4.5-36)/(4.5*5/3-2.5)
"""

上面我们只列出了程序一开始的部分。连续的三个双引号"""就是python中的“多行注释”命令,连续的三个单引号'''也是一样的,都可以使用。在两个“三引号”之间的文本,就是注释内容,上面的例子使用了三个双引号的方式。
注释内容对于python系统来讲不起任何作用,会在处理的时候忽略掉。用途只是让读程序的人,用人类理解起来更友好的语言描述程序的功能,帮助程序员记忆或者理解程序。

通过增加这样的注释,在我们自己重新读程序,或者别人借用我们的程序时,对程序会有更清晰的了解。
我们继续:

#定义用于计算甲乙两人速度的函数
#输入值:数值参数t,代表甲乙之间的距离
#返回值:甲、乙两人的速度
def getspeed(t):
    #计算乙的速度
    y=((12*4.5-t)/(4.5*5/3-2.5))
    #计算甲的速度
    x=((t-5*y)/3)
    #返回计算结果:甲、乙的速度
    return x,y

“#”读作“井号”,在python中用于单行的注释,"""'''是多行有效的,“#”只在本行有效,适合程序中小范围的注释。

除了注释,函数名称、变量名称的用心选取也能够帮助对程序的理解。比如上面getspeed,或者ji_suan_su_du这样的名字,就比speedxy这样的名字容易理解。当然在具体编程的时候会有权衡取舍的问题。单纯为了便于理解,把很多频繁使用的变量名取的很长反而会影响编程过程,就不如单独对某变量做一个清晰的注释,然后变量名用短一些的。
我们继续看完这个程序:

#计算原题:当甲乙双方相距36千米时双方的速度
x,y = getspeed(36)
print(x,y)

#计算延伸题:当甲乙双方相距48千米时双方的速度
x,y = getspeed(48)
print(x,y)

这部分是调用定义好的函数,来计算相距不同距离的时候,甲、乙的速度。有了注释的帮助,程序读起来显然容易多了。把注释用到程序的教学中,也是格外见效。
程序的开发通常会有很多文档性的要求,在不同的规则中,有些文档需要,在另外一些规则中,某些文档可能不需要。但源码都是必不可少的部分,所以把必要的文档保留在源码中是最重要的好习惯之一。

注释的内容可多可少,目的就是让人看懂。但是“看懂”是个很主观的事情,什么样叫做“看懂”呢?一般的原则是,在编写注释的时候,把自己对当前程序的知识清空,假设自己对这个程序一无所知,然后来读自己写的注释,看是否能理解注释和所注释的程序。或者也可以用“换位思考法”,把自己设想成其他人,来阅读自己的程序,看能否在注释的帮助下完全理解。
业界有一句经典:“好的程序会说话”,指的就是程序有良好的注释及良好的结构,从而有良好的可读性。

现在我们已经完整的注释了昨天的练习程序。为了解释起来方便,我们把程序拆分成了三部分,并没有删减。如果断断续续看起来觉得不顺畅,也可以读一下资料包中的code1.py源码。


接着就是如何做到对用户友好,我们现在的程序对用户可不友好。看看这个输出,你觉得除了编程序的人,还有别人能看懂吗?

6.0 3.6
14.0 1.2

为了让用户能看得懂,我们应当提供更多的辅助性说明信息,来描述程序的功能和计算结果,比如我们对比下面一个执行结果:

原题:
甲、乙两人相距36千米,相向而行.
如果甲比乙先走2小时,那么他们在乙出发2.5小时后相遇;
如果乙比甲先走2小时,那么他们在甲出发3小时后相遇;
甲、乙两人每小时各走多少千米?(假设甲乙的速度均匀稳定)

当甲乙双方相距36千米时:
甲方速度为: 6.0 千米,乙方速度为: 3.6 千米
当甲乙双方相距48千米时:
甲方速度为: 14.0 千米,乙方速度为: 1.2 千米

哪一个对用户更友好完全是不言而喻的。接下来我们看看如何做到这一点:

"""
程序名:函数式编程解应用题练习
作者:andrew
时间:2018年6月1日
"""
question="""
原题:
甲、乙两人相距36千米,相向而行.
如果甲比乙先走2小时,那么他们在乙出发2.5小时后相遇;
如果乙比甲先走2小时,那么他们在甲出发3小时后相遇;
甲、乙两人每小时各走多少千米?(假设甲乙的速度均匀稳定)
"""
"""
解题思路:
假设甲的速度为x千米/小时
假设乙的速度为y千米/小时
列方程组:
(2.5+2)x+2.5y=36
3x+(3+2)y=36 
根据方程2推导得出:
x=(36-5y)/3
代入方程1得出:
y=(12*4.5-36)/(4.5*5/3-2.5)
"""

......
......这里我们忽略掉函数定义的部分,因为没有变化
......

#显示原题
print(question)
#计算原题:当甲乙双方相距36千米时双方的速度
print("当甲乙双方相距36千米时:")
x,y = getspeed(36)
print("甲方速度为:",x,"千米,乙方速度为:",y,"千米")

#计算延伸题:当甲乙双方相距48千米时双方的速度
print("当甲乙双方相距48千米时:")
x,y = getspeed(48)
print("甲方速度为:",x,"千米,乙方速度为:",y,"千米")

程序在一开始的部分还是注释,使用三引号的注释方法。跟上一个程序的区别是,在原题描述的部分,我们把注释断开了。之前的部分,两组三引号圈住,完成了第一个注释。接着是:

question="""
原题:
甲、乙两人相距36千米,相向而行.
如果甲比乙先走2小时,那么他们在乙出发2.5小时后相遇;
如果乙比甲先走2小时,那么他们在甲出发3小时后相遇;
甲、乙两人每小时各走多少千米?(假设甲乙的速度均匀稳定)
"""

同样是两组三引号圈住一段多行文字,但使用“=”赋值符号,赋值给了变量question。这段文字叫做“字符串”,变量question就是“字符串变量”。名字的来源是这样:如果每个字符都是一个元素的话,这么多字符连接在一起,就好像是珍珠项链一样串接着,所以叫“字符串”。

同数字一样,字符串是python的基本数据类型的一种,python一共有6种基本的数据类型,我们的课程后续会逐步讲解,但并不会全部重点介绍。

注意这里“数据类型”的概念非常重要,无论是数字还是字符串,不同的类型,可以进行的操作是不一样的。比如数字可以加、减、乘、除计算。字符串就不可以,当然后面我们会讲字符串也有自己的运算,而这些运算,数字又不可以。

跟能够输出数字变量的值一样,print函数也可以输出字符串变量中的值。所以程序在显示的时候,先显示了question变量的值,也就是输出了原题部分的文字内容:

print(question)

所产生的输出结果是:

原题:
甲、乙两人相距36千米,相向而行.
如果甲比乙先走2小时,那么他们在乙出发2.5小时后相遇;
如果乙比甲先走2小时,那么他们在甲出发3小时后相遇;
甲、乙两人每小时各走多少千米?(假设甲乙的速度均匀稳定)

请注意输出的最后,也就是第6行,有一个空行。在第二讲我们已经说过了“回车符”或者“换行符”,这个符号是看不到的,只是把输出点,或者说光标点,换到下一行一开始的位置。
每个print函数在输出的时候,最后都会加一个换行符,从而使得下一次的输出,是从下一行一开始进行的。想想我们上一个例子最后输出的样子:

6.0 3.6
14.0 1.2

这两组数字,分成两行输出,就是因为第一个print函数执行的最后,附加了一个“换行符”。

回到刚才字符串的例子。使用三引号圈住多行字符串的时候,实际每一行的最后,都有一个不可见的“回车换行符”,这才有了行的概念。在圈住原题说明的最后一行,三引号是从下一行的开始圈住的,这里面就包含了一个不可见的换行符,这也是题目文字部分最后一行的结尾。在输出题目文字的时候,print附加了一个换行符,没有任何内容的一个新行,再换行,这就是第6行整行空行的由来。

随后的每一组输出,我们都使用字符串进行输出的提示:

print("当甲乙双方相距36千米时:")

这里使用一组两个单双引号圈起来的部分,也是一个字符串,比刚才的题目文字要短很多,另外重点是在一行之内。一组三引号,无论是三个双引号、还是三个单引号,都表示圈起来多行的字符串。当然在一行内使用也没问题,只是显得麻烦。
而一组单个的引号,无论是双引号还是单引号,都表示不跨行的字符串。所有这些用各种引号引起来的字符串,同数字的立即数一样,也是立即数,只是类型是“字符串类型”。

print函数的功能是输出参数的值。当然字符串的值也可以输出。

前面同时输出两个数字的时候,我们使用了print(x,y)这样的形式。这是因为print可以接受多个参数,参数中间使用逗号隔开。实际上所有的函数,使用多个参数的时候,都是这种格式,这是语法的要求。

print输出多个参数的时候,每输出完一个参数,会附加一个空格,这个空格就是刚才例子中6.0 3.6两个数字之间的空格。print函数并不限定所有的参数是什么类型,只要是python合法的数据,就可以被print输出,并在参数中间使用空格隔开。因为这种特征,我们为每个输出,都使用字符串进行了输出的提示,就是下面这句:

print("甲方速度为:",x,"千米,乙方速度为:",y,"千米")

输出的效果是:

甲方速度为: 6.0 千米,乙方速度为: 3.6 千米
 ...
甲方速度为: 14.0 千米,乙方速度为: 1.2 千米

看起来是不是清楚多了呢?


字符串的总结

由多个字符组成的文本就是字符串,并且使用“界定符”圈起来。不超过一行的字符串,界定符可以使用双引号“"”或者单引号“''”。多行字符串则必须使用三个单引号'''或者三个双引号"""。界定符必须成对儿使用,两个界定符之间的部分,才会被认为是字符串。更精确的讲是“字符串立即数”,回忆一下,立即数是跟变量对应的讲法,立即数是常量。

但是井号“#”并不是界定符,也不用成对使用。井号只有当做注释符一种用法,不能用来表示字符串。

一个字符串不赋值给任何变量,可以当做注释来使用,就好像我们前面见过的那样,虽然我们使用多行字符串举例,但单行字符串也是一样的。
把字符串不赋值给任何变量,相当于忽略掉了字符串这个值。这跟有些函数我们不使用返回值,等于忽略掉返回值是完全一样的方法。所以你可以推理一下,实际上数字的立即数,也可以不赋值给任何变量,这时候这个数字同样也会被忽略。只是这种用法完全没有意义,所以正常编程中几乎见不到。

43#数字立即数,不赋值给任何变量,这一行会被忽略
34+7#数字表达式,计算结果不赋值给任何变量,也会被忽略
print("test")#一个输出,来验证程序正常的执行

以上面几行程序为例,程序可以正常执行,没有任何报错信息,执行结果只是输出一行“test”字符串。

我们来看几个字符串的例子:

'好好学习,天天向上'#字符串,没有赋值给变量,会被忽略,可以用于注释

"好好学习,天天向上"#字符串,同上

'''好好学习,天天向上'''#字符串

'''好好学习,
天天向上'''#多行字符串,包括两行内容

"""好好学习,天天向上"""#字符串

'好好学习,天天向上"#前后界定符不匹配,会报错

a="好好学习,天天向上"#字符串赋值给变量

这几个概念不困难,相信你能看明白。而且,你看在注释的帮助下,我们对每一行的代码理解更清晰了,并且及时的就能看到包含在注释中的讲解。

学习过c语言的同学可能会问,为什么python中会有4种字符串界定符。原因很简单,用起来更方便,也防止混淆。比如在下面几个例子:

"today is tina's birthday"#防止字符串中的单引号造成混淆

'tom said: "come back jerry!"'#防止字符串中的对话引用混淆

"""
tom said:" i'm afraid not!"
"""#单、双引号都有混淆的可能,所以使用三引号

'''tom said:" i'm afraid not!"'''#跟上面一行意思一样

类似c语言这种没有那么多种界定符的语言,python中同样可以使用“转义符”来处理字符串内容可能造成混淆的情况,比如:

'tom said:" i\'m afraid not!"'

为了防止字符串内部的单引号造成混淆,在单引号前面使用了转义符“”,所有字符串中,当转义符出现的时候,其后面一个字符都将视情况不同,而做其它的处理,防止其本身的含义会造成混淆。在这一例中,“'”被强制当做字符串的内容来处理,而不是界定符,这样就不会同界定符单引号所混淆。字符串中的双引号,本身就不会同外面的单引号界定符混淆,所以不用特殊处理。

转义符的概念还有很多,我们可能会经常用到的还有换行符:“\n”,制表符:“\t”,反斜线字符本身“\”。换行符我们前面见过多次了,在字符串中使用换行符,除了使用多行字符串,还可以在需要的位置使用这里讲的换行转义符。另外字符串中比如会有可能用到反斜线自己,这时候就要键入两次反斜线,意思是“转义反斜线自己”。来看一个简单的例子:

>>> print("abc\n def\t321")#看到>>>,你应当想到这使用了交互模式
abc
 def321

上例的输出中,“abc”不会有疑问,换了一行继续输出,这就是换行转义符的功效。“def”之前有一个空格,这是字符串中\n之后那个空格。def跟321中有一个制表符。事实上如果你尝试多输出几行不同的内容,并且使用制表符,你会发现制表符可以把内容排列的非常整齐。另外再说一点你就更清楚了,在前面讲缩格的时候,我提到可以在需要缩格的地方使用一个“tab键”,事实上键盘上tab键输入的,就是制表符。只是跟换行符一样,虽然能看到效果,但是制表符也一样不可见,所以在需要使用制表符的地方,通常都是使用\t转义符。

前面两讲我们熟悉了数字的运算,下面我们看看字符串的常用运算。首先是字符串相加。我们使用python的交互模式来展示一下:

>>> 123+456#数字类型的+运算
579

>>> "好好学习,"+"天天向上"#字符串类型的+运算
'好好学习,天天向上'

#字符串之后是另外一个字符串,空格有没有都可以,这是另外形式的+运算
>>> "好好学习," '天天向上' 
'好好学习,天天向上'

#字符串的+操作经常用于组合各种复杂的格式,后面我们会经常用到
>>> a = "今天天气:" '晴,' '东南风2-3级,' "雾霾指数:" '0'
>>> print(a)
今天天气:晴,东南风2-3级,雾霾指数:0
#注意使用print输出,输出的只是内容本身,并不像交互模式那样两边还有引号

python字符串的乘法操作非常有创意:

>>> "好好学习,"*3
'好好学习,好好学习,好好学习,'

>>> "好好学习,"*3+"天天向上"
'好好学习,好好学习,好好学习,天天向上'

>>> 1234567*3
3703701

>>> "1234567"*3
'123456712345671234567'

现在我们已经学习了python的两种数据类型。经常有很多操作,是需要两种类型互相转换的,我们同样用交互模式来展示:

>>> int("1234567")*3#int()我们前面学过了,把小数变成整数用过
3703701
>>> float("3.14")*3#字符串转换成小数(浮点数)
9.42
>>> str(1234)*3#把数字转换成字符串
'123412341234'

#下面这个例子我们在前面看过:
>>> x=6.0
>>> y=3.6
>>> print("甲方速度为:",x,"千米,乙方速度为:",y,"千米")
甲方速度为: 6.0 千米,乙方速度为: 3.6 千米

#下面是把x/y先转换为字符串,然后整体使用字符串+操作,最后是1个字符串
>>> print("甲方速度为:"+str(x)+"千米,乙方速度为:"+str(y)+"千米")
#输出的时候,没有必要的空格不见了
甲方速度为:6.0千米,乙方速度为:3.6千米

上例中,最后的部分,我们展示了把数字变量,先转换为字符串,然后使用字符串的+操作把多个字符串组合为一个更复杂的字符串,然后使用print函数一次性输出。这是最常见的字符串应用场景。

我们在电脑上操作,编写程序,使用编辑软件,敲击键盘,实际全部都是字符串的操作。只是输入到电脑中之后,在各种电脑程序的支持下,把输入的内容分别当做不同的类型来对待,最常见的就是字符串转换成数字。

在python程序中,即时获取用户的输入使用input()函数,函数返回值永远是字符串类型。如果你需要获取数字,则需要使用前面讲过的字符串转数值操作,int()或者float()。有一点需要特别注意,如果用户输入错误,应当输入数字而输入了字符或者无意义的符号,那在转换的时候python会失败报错。

>>> input()#使用input()要求用户输入
abc#这里实际是python在等待用户输入,用户输入了abc
'abc'#input()返回值将是字符串类型的'abc'

>>> a=input('请输入你的年龄:')
请输入你的年龄:18
>>> print(float(a))
18.0
>>> a=input('请输入你的年龄:')
请输入你的年龄:3a
>>> print(float(a))
traceback (most recent call last):
  file "<stdin>", line 1, in <module>
valueerror: could not convert string to float: '3a'

练习时间

检验学习效果最好的方法就是做练习,想快速成长也是做练习。今天的练习内容是这样:

请让用户输入两个数字,并求和,输出求和结果。请尽力保持程序的友好。


本讲小结

保持程序的易读性是程序员永远追求的目标,因为不管你的脑子有多好,总有一天你会忘记。而效率的提高,依赖你经验的增长和程序代码的积累。程序代码的积累要求你的程序具有良好的可读性和可维护性。

本讲讲解了为程序增加注释的方法,和字符串的基本用法。保持程序的易用性需要大量用到字符串,向用户给出细致、准确的提示。字符串操作是几乎所有电脑程序最主要的工作之一。


练习参考答案

请参考源码ex.py


附注:前面提到了用户输入不合理导致的异常报错,想避免这个问题可以使用python中的异常处理功能。异常处理不属于本课程的讲授范围,有兴趣的可以查询try/except异常处理操作自己了解。