虽然每种编程语言都不同(尽管区别并没有设计者所说的那么大),但在某些方面它们是相关的。 低级还是高级:指是否在机器层面使用指令和数据对象编程(例如,将64位数据从一个位置移动到另一个位置),或者是否使用由语言设计者提供的更加抽象的操作来编程(例如,在屏幕上弹出一个菜单)。 通用还是指定应用领域:指编程语言的原始操作符应用领域是很广泛还是专门针对某一领域。例如,Adobe Flash的设计目的是快速为网页添加动画和交互,你绝对不想用它来构建一个股票投资组合分析系统。 解释还是编译:指的是直接由解释器运行编程人员写的指令序列,还是先把这个序列用编译器转换成机器级别的原始操作符。编程人员写的代码称为源代码。(在计算机发展的早期,人们需要使用非常接近机器码的语言来编写源代码,从而让它可以直接被计算机硬件解释。)这两种方法各有优势。使用解释型语言编写的代码通常更容易调试,因为解释器可以提供和源代码联系更紧密的错误信息。而编译型语言通常可以让程序运行得更快并使用更少的空间。 在本书中,我们使用Python。然而,这并不是一本关于Python的书。虽然本书的确会指导读者学习Python,但更重要的是可以让读者学会如何编写可以解决问题的程序。这项技能可以应用在各种编程语言上。 Python是一门通用编程语言,如果不需要直接访问计算机硬件,Python可以高效地构建几乎所有类型的程序。然而,Python并不适合编写需要高可靠性的程序(因为它的静态语义检查很弱),需要多人参与或者开发周期很长的程序(同样因为它的静态语义检查很弱)。 然而,Python比起其他语言确实有不少优点。相对来说,Python很容易学,因为它是解释型语言,可以提供很多运行时的反馈,对新手程序员来说非常有用。此外,Python有大量的免费库可以使用,极大地扩展了Python的功能。本书也会涉及一些库。 现在我们准备开始学习一些Python的基本元素。这些概念几乎对所有编程语言都是通用的,虽然在具体细节上不完全相同。 需要注意的是,本书并不会详尽介绍Python。我们只是把Python当作一种手段,目的是展示计算性问题的思考和解决方法。因此,本书只会涉及语言的部分特性,不会介绍我们不需要的Python特性。已经有非常优秀的在线资源全面介绍Python,所以我们不需要覆盖Python的所有内容。在教授本书以之为基础的课程时,我们会把这些免费的在线资源作为参考资料推荐给学生。 Python是一门不断演进的语言。自从1990年吉多•冯•罗苏姆(Guido von Rossum)发布Python以来,它经历了很多变化。最初的十年中,Python知名度很低,无人问津。转折点是2000年Python 2.0的发布,除了语言本身有大量重要的改进,它还标志着语言进化路径的改变。很多人开始开发可以与Python无缝结合的库,对Python生态系统持续性的支持和发展变成了一种社区驱动的活动。Python 3.0在2008年底发布。这个版本的Python解决了Python 2的多个发布版(通常称为Python 2.x)在设计上的不一致。然而,它并不向后兼容,也就是说大部分使用之前版本Python编写的程序并不能在Python 3.0上运行。 在本书中,向后不兼容也是一个问题。在我们看来,Python 3.0相比Python 2.x有很大的优势。然而,在写作本书的过程中,一些重要的Python库仍然不支持Python 3。因此,在本书中我们会使用Python 2.7(在这个版本中,Python 3的很多重要特性都可以被“逆向移植”)。 2.1 Python的基本元素 Python程序有时被称为脚本,是一系列定义和命令。Python的解释器,有时被称为shell,会对定义求值并执行命令。通常来说,开始执行程序时会创建一个新shell。大多数情况下,会有一个窗口关联到shell上。 我们建议你现在打开一个Python shell,并尝试本章中包含的一些例子。而且,请在本书的之后章节中也动手尝试。 一条命令,通常被称为一个语句,指示解释器去做一些事情。例如,语句print 'Yankees rule!'指示编译器把字符串Yankees rule!输出到和shell相关联的窗口中。 命令序列: print 'Yankees rule!' print 'But not in Boston!' print 'Yankees rule,', 'but not in Boston!' 会使解释器产生如下输出: Yankees rule! But not in Boston! Yankees rule, but not in Boston! 注意,在第三条语句中我们传给print两个值。print命令接收任意数量的值并按照值的出现顺序打印它们,以一个空格字符分割 。 2.1.1 对象、表达式和数值类型 对象是Python程序处理的核心事物。每个对象都有类型,类型定义了程序可以通过对象做什么。 类型分为标量和非标量。标量对象是不可分的,可以把它们看做构成语言的原子 。非标量对象,比如字符串,有内部结构。 Python有四类标量对象。 int用来表示整数。int类型的字面量写法很直观,例如3、5或者10 002。 float用来表示实数。float类型的字面量写法包含一个小数点,例如3.0、3.17或者28.72(虽然很少用到,但是float类型的字面量可以使用科学计数法表示。例如,字面量1.6E3代表1.6*103,即1600.0)。你可能很好奇为什么这个类型不叫real。在计算机中,float类型的值被作为浮点数存储起来。这种表示法被所有现代编程语言采用,具有很多优点。然而,在某些情况下这种表示法会导致浮点运算和实数运算稍有不同。我们会在3.4节详细讨论。 bool用来表示布尔值True和False。 None是只包含一个值的类型。我们会在变量部分介绍。 对象和操作符可以组合成表达式,每个表达式都表示某种类型的对象,我们称之为表达式的值。例如,表达式3+2表示int类型的对象5,表达式3.0+2.0表示float类型的对象5.0。 ==操作符用来判断两个表达式是否相等,!=操作符用来判断两个表达式是否不相等。 >>>符号是shell提示符,表示解释器希望用户向shell中输入一些Python代码。提示符下面的一行表示解释器对输入的Python代码求值得到的结果,具体的交互示例如下所示: >>>3 + 2 5 >>>3.0 + 2.0 5.0 >>>3 != 2 True Python内建的type函数可以用来获取一个对象的类型: >>> type(3) <type 'int'> >>> type(3.0) <type 'float'> int类型和float类型支持的操作符如图2-1所示。 图2-1 int和float类型支持的操作符 算数操作符有通常的优先级。例如,*的优先级比+更高,所以表达式x+y*2的求值过程是先计算y乘以2,然后再把结果和x相加。求值顺序可以通过括号改变,例如,(x+y)*2首先将x和y相加,然后再把结果和2相乘。 bool类型支持的运算符。 a and b,如果a和b都是True,结果就是True,否则是False。 a or b,a和b至少有一个是True时结果是True,否则是False。 not a,如果a是False,结果是True,如果a是True,结果则是False。 2.1.2 变量和赋值 变量可以将名称和对象关联起来。看下面这个例子: pi = 3 radius = 11 area = pi * (radius**2) radius = 14 它首先把名称pi 和radius绑定到不同的int类型对象上,然后把名称area绑定到第三个int类型对象上,如图2-2中左图所示。 图2-2 把变量绑定到对象 如果程序之后执行redius=11,名称radius会被重新绑定到另一个int类型的对象上,就像图2-2中右图所示。注意,这个赋值并不会影响area绑定的值,它仍然绑定在表达式3*(11**2)表示的对象上。 在Python中,变量仅仅是名称,没有其他意义。记住这一点,这非常重要。同样要记住,一条赋值语句会把等号左侧的名称和右侧的表达式所表示的对象关联起来。一个对象可以和一个名称或多个名称关联起来,也可以不关联任何名称。 可能我们不应该说“变量仅仅是名称”。并不像朱丽叶说的那样 ,名字其实是很重要的。通过编程语言我们可以将计算用机器可以执行的方式描述出来,但这并不意味着只有计算机会阅读你的程序。 之后你就会发现,编写一个可以正确工作的程序并不简单。有经验的程序员证实,他们花费了大量时间在阅读程序上,试图搞清楚程序到底在做什么。因此编写易读的程序非常重要。变量名的选择在增强可读性方面起着重要的作用。 看下面两个代码片段: a = 3.14159 pi = 3.14159 b = 11.2 diameter = 11.2 c = a*(b**2) area = pi*(diameter**2) 从Python的角度来说,它们没有区别。运行时,它们会做同样的事。然而,对于人类读者来说,它们有非常大的区别。当我们阅读左侧的代码片段时,没有任何先验知识,所以无法判断是否出错。然而,右侧的代码一眼就可以看出有错。要么是diameter变量应该被命名为radius,要么是在计算面积时应该将diameter除以2.0。 在Python中,变量名可以包含大写字母和小写字母、数字(但是不能处在变量名的开头)以及特殊字符_。Python的变量名是区分大小写的,例如,Julie和julie是不同的名称。最后,Python中有一些保留字(有时称作关键字),它们有内置的意义,所以不能作为变量名。不同版本的Python保留字会稍有不同。Python 2.7中的保留字是and、as、assert、break、class、continue、def、del、elif、else、except、exec、finally、for、from、global、if、import、in、is、lambda、not、or、pass、print、raise、return、try、with、while以及yield。 另一个增加代码可读性的方法是添加注释。符号#后面的文本不会被Python解释。例如,你可以这么写: #从圆c的面积中减去正方形s的面积 areaC = pi*radius**2 areaS = side*side difference = areaC-areaS Python允许多重赋值。语句: x, y = 2, 3 会将x绑定到2并将y绑定到3。赋值语句右侧的表达式会在绑定更改之前求值,因此你可以非常方便地用多重赋值来交换两个变量的绑定。 例如,代码: x, y = 2, 3 x, y = y, x print 'x =', x print 'y =', y 会输出: x = 3 y = 2 2.1.3 IDLE 直接在shell中写程序很不方便。大部分程序员会使用某种文本编辑器,这些编辑器是集成开发环境(IDE)的一部分。 在本书中,我们会使用IDLE ,这是Python标准安装包中自带的集成开发环境。IDLE是一个应用程序,就像你计算机上的其他应用程序一样。你可以像启动其他应用程序一样启动它,例如,双击图标。 IDLE提供: 一个支持代码高亮、自动补全和智能缩进的文本编辑器; 一个支持代码高亮的shell; 一个集成调试器,你可以暂时忽略它。 IDLE启动时会打开一个shell窗口,你可以在其中输入Python命令。它也会提供一个文件菜单以及编辑菜单(就像其他菜单一样,现在你完全可以忽略它们)。 文件菜单包括以下命令: 创建一个新的编辑窗口,你可以在其中输入Python程序; 打开一个包含Python程序的文件; 将当前编辑窗口中的内容保存到一个文件中(文件扩展名是.py)。 编辑菜单包含标准的文本编辑命令(例如,复制、粘贴和查找)以及一些特殊的命令来让你更容易编辑Python代码(例如,缩进或者注释代码块)。 如果要查看对于IDLE的完整描述,可以访问http://docs.python.org/library/idle.html。 2.2 分支程序 到目前为止我们看到的计算类型称为直线程序。它们按照语句的出现顺序一条接着一条地执行语句,并在执行完所有语句之后停止运行。通过直线程序可以描述的计算并不是很有意思,或者说,非常无聊。 分支程序更有意思。最简单的分支语句是条件语句。就像图2-3所示,一个条件语句有三部分。 图2-3 条件语句的流程图 一个测试,即对一个表达式求值,结果是True或者False; 一个代码块,在测试结果为True时执行; 一个可选的代码块,在测试结果为False时执行。 在一条条件语句执行之后,会接着执行之后的语句。 在Python中,条件语句的形式是: if Boolean expression: block of code else: block of code 在描述Python语句的形式时,我们使用斜体来描述可以出现在程序中的代码类型。例如,非终结符Boolean expression表示任何可以求值为True或者False的表达式都可以跟在关键字if之后,非终结符block of code表示任何Python语句序列可以跟在else:之后。 请看下面的程序,如果变量x的值是偶数会输出Even,否则输出Odd: if x%2 == 0: print 'Even' else: print 'Odd' print 'Done with conditional' 当x除以2的余数是0时,表达式x%2==0的值是True,否则是False。要记住==用来进行比较,因为=被用来赋值。 缩进在Python的语义中是有意义的。例如,如果上面的代码中最后一条语句被缩进了,那它会成为和else关联的代码块中的一部分,而不是跟在分支语句之后的代码块。 像Python这样处理缩进并不常见。大部分编程语言使用括号符号来区分代码块,例如,C使用大括号{}来包裹代码块。Python的缩进处理方法有一个好处,代码的视觉结构和实际的程序语义结构是完全一致的。 条件语句的嵌套是指条件语句中的真代码块或者假代码块包含另一个条件语句。在下面的代码中,顶层的if语句所包含的两个分支都包含嵌套条件。 if x%2 == 0: if x%3 == 0: print 'Divisible by 2 and 3' else: print 'Divisible by 2 and not by 3' elif x%3 == 0: print 'Divisible by 3 and not by 2' 上面代码中的elif表示“else if”。 在条件语句的测试中使用复合布尔表达式是很方便的,例如: if x < y and x < z: print 'x is least' elif y < z: print 'y is least' else: print 'z is least' 条件语句让我们可以编写比直线程序更有意思的程序,但它仍然是一类非常有限的程序。我们可以从另一个角度,通过对比它们的运行时间来考虑程序的性能。假设每行代码需要1个单位时间来运行,那么一个包含n行代码的分支程序表现如何?它的执行时间可能少于n个单位时间,但是绝对不可能超过n个单位时间,因为每行代码最多只能运行一次。 如果一个程序的最大运行时间是由程序长度决定的,那么它被称为以常数时间运行。这并不是说每次的运行时都执行同样数量的步骤,而是说存在一个常数k,这个程序的运行步骤保证不会超过k。也就是说,程序的运行时间并不会随着输入的增加而增加。 常数时间的程序可以做的事非常少。试想一下,编写一个程序来记录选举中的投票,很难想象可以写出一个运行时间独立于投票数目的程序。实际上,可以证明这是不可能的。对问题固有难度的研究被称为计算复杂度。之后我们还会多次提到它。 幸好,我们只需要另一个编程语言的结构就可以编写任意复杂度的程序,那就是迭代,2.4节会介绍。 动手练习:编写一个程序,处理三个变量x、y和z,然后输出它们当中最大的奇数。如果其中没有奇数,输出一条信息来说明。 2.3 字符串和输出 字符串(str)类型的对象用来表达字符串 。字符串类型字面量可以使用单引号或者双引号表示,例如,'abc'或者"abc"。字面量'123'表示一个字符串,不是数字123。 试着在Python的解释器中输入下面的表达式(别忘了>>>是提示符,不需要输入): >>> 'a' >>> 3*4 >>> 3*'a' >>> 3+4 >>> 'a'+'a' 操作符+实现了重载。应用在不同类型的对象时它有不同的意义,例如,应用到两个数字时它的意义是相加,应用到两个字符串时它的意义是连接。操作符*同样实现了重载。当两个操作数都是数字时,它的意义和你想的一样。当应用到一个数字和一个字符串上时,它会复制字符串。例如,表达式2*'John'的值是'JohnJohn'。这其中有一定的逻辑。就像表达式3*2和2+2+2相同一样,表达式3*'a'和'a'+'a'+'a'相同。 现在试着输入: >>> a >>> 'a'*'a' 这两行都会产生错误信息。 第一行产生如下错误信息: NameError: name 'a' is not defined 因为a并不是某一个类型的字面量,解释器会把它当作一个名称。然而,由于这个名称并没有绑定到任何对象上,尝试使用它会导致运行时错误。 代码'a'*'a'产生如下错误信息: TypeError: can't multiply sequence by non-int of type 'str' 存在类型检查是一件好事,可以将粗心大意(有时是过于聪明)转换成会停止运行的错误,而不是导致程序行为无法预测的错误。Python中的类型检查并不像有些语言(例如,Java)那样强大。例如,用来比较两个字符串或者两个数字时,<的意义非常明确。但是'4'<3的值应该是什么?Python的设计者相当武断地决定它应该是False,因为任意数字的值都应当比字符串的值小。有些语言的设计者认为这样的表达式没有明确的含义,因此应当产生一个错误信息。 字符串是Python中的序列类型之一。所有的序列类型都支持下面的操作符。 字符串的长度可以通过len函数获取。例如,len('abc')的值是3。 下标可以用来获取字符串中的单个字符。在Python中,所有的索引都是从0开始。例如,在解释器中输入'abc'[0]会输出字符串'a'。输入'abc'[3]会产生一个错误信息IndexError: string index out of range。Python用0来代表字符串的第一个元素,所以长度为3的字符串的最后一个元素要通过下标2来获取。负数用来从字符串结尾开始索引。例如,'abc'[-1]的值是'c'。 切片用来获取任意长度的子字符串。如果s是一个字符串,表达式s[start:end]表示s中从下标start开始到下标end-1截止的一个子字符串。例如,'abc'[1:3]='bc'。为什么要在下标end-1处截止,而不是在end处截止呢?因为这样的话表达式'abc'[0:len('abc')]就会返回期望的值。如果冒号前面的值被省略,默认值为0。如果冒号后的值被省略,默认值是字符串的长度。因此,表达式'abc'[:]在语义上等同于繁琐的'abc'[0:len('abc')]。 输入 Python 2.7有两个函数(第4章会讨论Python中的函数)可以用来直接从用户处获取输入,分别是input和raw_input 。它们会接受一个字符串作为参数并马上将它显示在shell中,然后等待用户输入一些内容并按下回车键。对于raw_input来说,输入被当作一个字符串并作为函数的返回值;input会把输入当作Python的一个表达式并推测它的类型。在本书中,我们只使用raw_input,相比之下它可以让程序的行为更容易预测。 考虑下面的代码: >>> name = raw_input('Enter your name: ') Enter your name: George Washington >>> print 'Are you really', name, '?' Are you really George Washington ? >>> print 'Are you really ' + name + '?' Are you really George Washington? 注意,第一个print语句在?前面添加了一个空格。这是因为当print接受多个参数时会在参数的值之间添加一个空格。第二个print语句使用连接来生成一个字符串,并不包含多余的空格,然后将这个字符串当作唯一的参数传入print。 现在考虑如下代码: >>> n = raw_input('Enter an int: ') Enter an int: 3 >>> print type(n) <type 'str'> 注意,变量n被绑定到字符串'3'上,并不是数字3。因此,举例来说,表达式n*4的值是'3333'而不是12。好消息是只要字符串的字面量符合其他类型,就可以做类型转换。 类型转换在Python代码中很常见。我们使用一个类型的名称来将值转换到那个类型。因此,举例来说,int('3')*4的值是12。当一个浮点数被转换成整数时,小数部分会被截断,例如,int(3.9)的值是整数3。 2.4 循环 图2-4描述的是通用的循环机制。就像条件语句一样,循环以一个测试开始。如果测试的值为True,程序会运行一次循环体,然后重新对测试求值。这个过程不断重复直到测试的值为False,然后控制流会继续执行循环语句后面的代码。 图2-4 循环的流程图 考虑下面的例子: #计算整数平方根的复杂方法 x = 3 ans = 0 itersLeft = x while (itersLeft != 0): ans = ans + x itersLeft = itersLeft - 1 print str(x) + '*' + str(x) + ' = ' + str(ans) 代码首先把变量x绑定到整数3,然后不断重复加法来计算x的平方。下面的表格展示了每个变量在每次循环开始处进行测试前的值。我们通过手动模拟代码来构造这个表格,例如,我们把自己想象成一个Python解释器并使用铅笔和纸来执行程序。使用铅笔和纸可能显得有点过时,但使用这种方法可以很好地理解程序的行为 。 测试 # x ans itersLeft 1 3 0 3 2 3 3 2 3 3 6 1 4 3 9 0 第四次到达测试时,对它的求值为False,然后控制流会执行循环后面的print语句。 x为何值时程序可以终止? 如果x==0,itersLeft的初始值也会是0,循环体永远不会执行。如果x>0,itersLeft的初始值大于0,因此循环体将执行。 每次执行循环体时,itersLeft的值都会减1。也就是说如果itersLeft比0大,在有限次循环之后,itersLeft==0。这时循环的测试为False,控制流会执行while语句之后的代码。 x的值如果是-1会发生什么?会发生很糟糕的事。控制流会进入循环,然后每次循环都会让itersLeft离0越来越远,因此程序会无限执行循环(或者直到其他坏事发生,例如,溢出错误发生)。我们怎么解决这个问题呢?可以将itersLeft初始化为x的绝对值。循环将会终止,但是会打印一个负值。如果循环内的赋值语句修改为ans=ans+abs(x),代码就会正确工作。 到现在为止,我们已经介绍了Python中处理数字和字符串的相关内容,可以开始编写有趣的程序了。现在我们暂时停止学习语言,休息一下。下一章会使用Python来解决一些简单的问题。 动手练习:编写一个程序,要求用户输入10个整数,然后输出其中最大的奇数。如果没有输入奇数,输出一条信息来说明。
编程导论——第2章:Python介绍
书名: 编程导论
作者: [美] John V·Guttag
出版社: 人民邮电出版社
原作名: Introduction to Computation and Programming Using Python
译者: 梁杰
出版年: 2015-4
页数: 284
定价: 59.00元
装帧: 平装
ISBN: 9787115388018