《从问题到程序:用Python学编程和计算》——2.11 补充材料

2.11 补充材料

本书各章的主要内容将围绕着怎样通过编程解决计算问题展开,正文中对Python语言的机制只做必要的说明,有些细节情况没有涉及。另外,用Python编程也有许多有趣而且有用的技术。如果在各章的主要部分详细罗列,也可能冲淡讨论的主线。但是上述两方面的一些情况也值得介绍。本书采用的方法是在一些章的最后增加称为“补充材料”一节,补充一些细节,供读者参考,也供用本书教授课程的教师选用。
除了讨论语言细节和编程技术的两个小节外,有时还总结了一些常用的编程模式。练习中的第1题总是对本章内容做些总结,其中列出本章讨论过的重要概念。
2.11.1 语言细节
这里介绍Python语言中的一些与本章内容有关的情况,作为本章内容的补充。这里介绍的情况在本书中并不使用,对于初学者并不重要,可以有选择地阅读。提供这部分内容有两方面意义,首先是使本书中对Python语言的介绍较为完整。另一方面,读者在进一步使用Python去解决更复杂的问题时,也可能需要用到这里介绍的功能。
整数的二进制、八进制和十六进制字面量形式
Python只有一种整数,即是类型为int的整数。但另一方面,这个语言却为整数提供了多种不同的字面量描述方式。前面介绍的十进制字面量形式使用最普遍,但Python还提供了二进制、八进制和十六进制的整数字面量形式。现在介绍有关情况。
首先,二进制、八进制和十六进制的整数字面量形式,都由一个引导部分和一个数值部分组成,一个整数仍然写成一个连续的字符序列。这三种字面量的引导部分都以数字0开头,在0之后用一个字符相互区分,而后紧接着是一串表示其数值的数字(对于十六进制,其中还能包含一些英文字母,见下)。
二进制整数字面量的引导部分是0b或者0B,随后表示数值的部分中只能用两个数字0和1,是一段0/1序列。例如0b110101和0B110100。在b或B之后立刻出现几个0也允许,不影响结果的数值。二进制数的数值按如下方式计算:

这里实际上假设写出的二进制字面量包含n + 1位二进制数字。很容易看到,Python的二进制字面量只是整数的一种写法,例如:

>>> 0b110101
53
>>> 0B01001110
78
>>>

在二进制字面量里出现超出0和1的数字将作为出错。此外,Python标准函数bin返回与其整数参数对应的二进制字面量形式(结果是一个字符串)。例如:

>>> bin(78)
'0b1001110'

八进制整数字面量的引导部分用0o或者0O(这两种形式都很糟糕,0和O太像了),随后可以写出任意长的数字0到7的序列。其计值公式是:

例如:

>>> 0O12
10
>>> 0O1234567012345670
45954944846776

与二进制类似,表示数字的部分中不能出现数字8或9。标准函数oct返回与其整数参数对应的八进制字面量形式(是一个字符串)。
十六进制整数字面量的引导部分用0x或者0X。这里有一点麻烦:表示十六进制数的数值需要16个数字,但阿拉伯数字只有10个。Python采用计算机领域的习惯做法,用最前面6个英文字母填补空缺:a或A表示10,b或B表示11,c或C表示12,d或D表示13,e或E表示14,f或F表示15。下面是两个十六进制字面量:

>>> 0X12
18
>>> 0xdeadbeeffeeddeaf
16045690985374408367

十六进制数表示的计值公式与上面类似:

标准函数hex返回其整数参数对应的十六进制字面量字符串。
用二进制、八进制、十六进制字面量都可以描述任意大的整数。
整数的位运算
计算机里的数据都用二进制编码的形式表示,位(即二进制位,bit)是最基本的编码单位,也是最小的数据表示单位。在实际问题中,有些对象的变化情况很简单,只用一个或几个位就能表示,如果程序里这种数据很多,把它们表示为某类型的对象,可能造成很大的存储浪费。一种可能办法是把多个这类数据对象存入一个整数类型的对象。此外,一些与底层有关的程序可能需要操作二进制位数据。例如,硬件工作状态信息通常用二进制位串表示,操作硬件时就需要用位串形式发命令。为适应这些情况,Python语言提供了针对整数中二进制位的操作。有关的运算符称为按位运算符。
先介绍基本的位运算,它们是按位运算符的基础。一个二进制位只能取值0或1,位运算就是对二进制位的运算,从一个或两个0/1值出发,算出0/1结果。常用位运算共有四个,其中“否定”为一元运算,其他都是二元运算,运算规则很简单,见下表:

从这个表可以看出,否定就是1变0而0变1;只有1和1“与”的结果是1,其他情况的结果均为0;0和0“或”的结果为0,其他情况结果都是1;“异或”运算看两个被操作位是否相同,相同时是1,否则是0。
与上面的位运算相对应,Python语言定义了四个按位运算符,把它们作用于整型对象,得到整型结果。这些按位运算符把整数看成二进制位的序列。按位否定运算符是一元运算符,作用于一个整数类型的运算对象,其余三个都是二元运算符,作用于两个整数类型的运算对象。在运算时,它们对运算对象的一个个(或一对对)二进制位分别做位运算,得到结果的各位。四个按位运算符是:

看一个例子,假设变量x和y都是16位整数,它们值分别是:

x:  0010,1001,0101,0111
y:  1001,1100,1111,1010

对x和y做各种按位运算,得到的结果如下:

~x          1101,0110,1010,1000
x & y       0000,1000,0101,0010
x | y       1011,1101,1111,1111
x ^ y       1011,0101,1010,1101

请读者根据这个例子,对照上面说明,设法弄清各按位运算符的意义。注意,这里整数表示中的逗号只是为了阅读方便。
这些运算符可以作用于任意大的整数对象,得到任意大的结果。如果两个整数的长度不同,Python有默认的处理方式,总之不会丢失信息。
除了上面4个按位运算符外,Python还有两个与二进制位有关的整数运算符,分别是左移运算符“<<”和右移运算符“>>”。它们的左边是被位移的整数对象,右边的整数表示希望左移或右移的位数。举例说,对上面的y将有:

y << 3: 1110,0111,1101,0000
y >> 3: 0001,0011,1001,1111

可以看到,左移(或右移)空出的二进制位全部补0。
另请注意,上面的这六个运算符都是做运算,产生新的整数对象。例如3 << 3“算出”一个结果(一个新的整数对象24)。假设变量z的值是3,z << 3得到24,但z的值不变。要改变z的值就需要赋值,或者用下面介绍的扩充赋值运算符。
上述运算符的优先级情况有些复杂:一元否定运算符的优先级与一元正负号相同,移位运算符的优先级低于整数加减,三个二元按位运算符的优先级互不相同,从高到低依次为 &、|、^,但都低于移位运算符。所有二元运算符都按从左到右结合。例如x << 3 >> 5相当于(x << 3) >> 5。由于优先级的情况比较复杂,建议少写过分复杂的表达式,多用括号显式描述运算的顺序。
与上述5个二元位运算符相对应,Python有5个扩充赋值运算符,它们分别基于赋值符之后的第二个参数修改左边表达式的对象(最简单情况用变量表示):

例如,x <<= 5效果相当于x = x << 5,但书写方便,通常效率更高。
有关浮点数的进一步说明
高级语言的浮点数一般都直接映射到语言系统运行所在的计算机硬件,因此高级语言中的浮点数及浮点数计算直接反映了硬件的相关特征。在计算机设计层面,目前大多数硬件的浮点数计算都采用IEEE 754标准。这里的IEEE是电气和电子工程师协会的简写,它是目前全球最大的一个非营利性专业技术学会。IEEE 754是IEEE颁布的一个浮点数算术系统标准,被大多数计算机硬件厂商采纳。Python语言并没有强制性地规定采用IEEE 754浮点数(如果那样,在不执行这个标准的硬件上将很难实现Python),但常见环境中运行的Python中的浮点数应该符合这个标准。下面介绍最常用的浮点数情况。
在常规系统上运行的CPython都采用IEEE 754的双精度浮点数标准作为语言的浮点数。这种浮点数用64位二进制码表示,其中指数部分用11位,可以表示-1023到1023;表示数值的部分(术语是尾数部分)用52位,另有一个符号位表示正负数。
指数部分的大小决定了浮点数的表示范围,绝对值最大(最小)大约是±21023的量级。换算到十进制数,表示的范围(如前面所说)大致为±5×10-324~1.7×10308。尾数的位数决定了浮点数的表示精度,52位二进制数大约相当于十进制的16到17位。超过上述范围的实数在这里无法表示。即使在范围内,浮点数表示也受到精度的限制,只能表示数轴上一个个能用二进制编码形式表示的孤立点。
IEEE 754的具体表示方式还有些细节,但知道上面这些对于初学者已经够了。从基本编程的需要看,只需了解这种浮点数标准的表示范围和精度。许多其他语言的实现也采用IEEE 754的双精度浮点,其他Python实现多半也采用这个标准。
浮点数舍入转换
从浮点数转换到整数,默认转换方式是舍去小数部分,通常称为截尾。内置函数round采用另一种转换方式,通过舍入得到与浮点数最近的整数。但什么是最近呢?中小学的算术里教过“四舍五入”的舍入规则,但显然这一规则偏向于入,从统计的观点看,这样得到的整数值偏大。银行总按四舍五入付钱就会亏本,长期做下去累积的“入”也会导致亏本很多。为了防止这种情况,人们提出了另一种更为公平的舍入方式。
Python的round函数采用所谓“银行家舍入”方法,可称为“四舍六入五取偶”舍入,这也是目前常见计算机硬件采用的舍入计算标准(IEEE 754浮点数标准中的舍入计算标准方法,目前编程语言和工具大都直接借用这一舍入计算标准)。
具体说,如果需要舍入部分的最高位小于等于4和大于等于6,就直接分别舍去或者进位。假设需要舍入的那段数字的最高位是5,如果在这个5之后还有不为0的有效位(也就是说,明确地大于5),转换时就进位。如果5之后都是0位,则根据5的前一位舍入:前一位是奇数时进位,前一位是偶数时舍去。这里把0也看作偶数。与简单的四舍五入,“银行家舍入”在概率上更公平。按照这种规则,可以看到:

>>> round(0.5)
0
>>> round(1.5)
2
>>> round(-0.5)
0
>>> round(-1.5)
-2

字符串和换意序列
在写字符串时有些字符无法直接写出,换行符是这种字符的典型代表。由于有这种情况,Python(与其他一些语言一样)引入了换意序列的概念,用一种特殊形式的字符序列(包含两个或更多字符)表示字符串里的一个字符。换意序列的第一个字符总是下划线符,也就是说,字符串里出现的下划线符总表示换意序列开始,后面字符(序列)决定换意序列表示的字符。前面介绍过几个常用换意字符,下面是它们和另外几个及其解释:

其中,“\换行符”表示反斜线后紧跟换行。“\ooo”表示反斜线后紧跟3位八进制数字,这种形式的换意序列表示编码为八进制为ooo的字符。“\xhh”表示反斜线后有一个x,后跟2位十六进制数字(包括A/B/C/D/E/F),这种换意序列表示编码为hh的字符。
除上面这些表示形式外,Python还支持用于写出所有Unicode字符的换意序列形式。这方面的细节这里不进一步介绍了。
用一对三个引号的形式,在字符串字面量中可以包含换行,不必再写换意序列“\n”。此外,采用三个引号的形式,多数时候也不需要用单引号和双引号的换意序列“\'”和“\"”。但有时还可能需要,例如下面字面量没问题:

""""Is this
the book
of you?", she asked"""

开头的连续四个引号被正确解析为一个连续三引号和一个双引号。但

""""Is this
the book
of you?""""

将报错。开始的连续四个双引号可以正确解析,但最后的四个双引号不行:解释器看到前三个双引号,认为它是字符串的结束标志,字符串到此结束。又看到一个双引号,解释器认为另一个字符串从这里开始。遇到换行,它认为字符串没完,报语法错误。要正确写出这个字符串(内容是一对双引号括起的一句话),可以写:

""""Is this
the book
of you?\""""

或者更简单的,用一对三个单引号作为字符串括号。
如果在一行里连续写出几个字符串,解释器会自动将其连接成一个字符串:

>>> s = 'abc' "123"
>>> s
'abc123'

但是,解释器只是在处理字符串字面量,才这样做。例如:

>>> t = '456'
>>> s t
SyntaxError: invalid syntax

如果要拼接作为变量值的字符串,必须用拼接运算符(加法符号)。
基本语句
表达式可以写在程序中任何应该写语句的位置,这样的表达式就构成了一个表达式语句。在一般程序里,把普通表达式(例如算术表达式1 + 2等)作为语句使用意义不大。运行中执行这个语句时就求值该表达式,求出值后该语句的执行完成,表达式的值随后将被丢掉,不产生任何效果(除了表达式计算可能耗费计算机时间)。
有用的表达式语句主要是函数调用。在Python里,函数调用是一类基本表达式。独立写出的函数调用就构成了一个表达式语句。前面程序里已经多次出现这种表达式语句,例如对内置函数print的调用语句。实际上,任何无特定返回值的函数(实际上返回None),通常都以表达式语句的方式使用。
Python允许在一行中写多个语句,这时要求语句之间加分号。例如:

x = len; y = x + 1

这样一行仍看作一个语句,在其执行时将顺序执行其中的成分语句。最后的成分语句执行完毕时整个语句的执行完成。这种语句是顺序语句的一种形式。当然,这种形式中也可以写任何语句,不仅是赋值语句。例如在一个循环里写:

x = y + 4; continue

虽然语言允许上面的形式,有时也会看到有人把几个简单语句写在一行。但在Python编程实践中,人们不大倡导这种形式。在绝大多数Python程序里,人们坚持一行一个语句的基本规则。在一些情况下用并行赋值语句同时给几个变量赋值。
2.11.2 编程技术
本节讨论几个与编程有关的技术问题。
条件语句与条件表达式
在一些情况下,条件语句和条件表达式都能使用。例如下面同样函数的两个定义:

def abs(x):
    if x < 0:
        return -x
    else:
        return x

def abs(x):
    return -x if x < 0 else x

两个函数功能完全一样,但后一个简单许多。参考这个实例,可以总结出适合使用条件表达式的情况:在需要根据条件,从两种不同的表达式计算中选一个,而且计算比较简单时,采用条件表达式可以简化程序。条件语句适合用于各种赋值情况,其一个分支中可以包含任意复杂的操作序列。用来处理上面问题,是大材小用了。
在后面章节里,还会看到许多使用条件表达式的有意思的例子。
写表达式的技术
如果需要写的计算表达式非常复杂,应该设法做出安排,使写出的程序代码清晰易读,容易检查表达式书写的正确性。有两种方法可以参考。
其一是分解表达式,用一个或几个中间变量记录表达式中子部分的结果,而后用这些变量的值组合出最终的表达式。适当的分解有助于保证表达式的可读性和正确性。
如果确实需要或者希望写很长的表达式,那就需要安排好表达式的多行格式。首先做好准备,为能把表达式写在多个行里,可以参考前面基于三边求三角形函数中条件的写法,先加入一个括号保证解释器不会把表达式截断,而后在每行适当的地方断行,各行中属于同层的结构相互对齐(仿照Python语言的格式)。例如,下面是一个长表达式:

x = ((a1  a2 - b1  b2 - c1  c2 - d1  d2) +
     (a1  b2 + a2  b1 + c1  d2 - c2  d1) * i +
     (a1  c2 + a2  c1 + b2  d1 - b1  d2) * j +
     (a1  d2 + a2  d1 + b1  c2 - b2  c1) * k)

为能写跨越多行的长表达式,在上面表达式开始加了一个括号。在上述表达式中,我们把属于同一层的子表达式对齐,分别放在几行。这样写表达式,很容易检查,容易发现错误。IDLE或其他支持Python的开发系统都能帮助维持良好的表达式形式。但如何断行等,还是需要人做好安排。
数值计算函数中错误情况的返回值
有些数值计算函数不是全函数(相对于参数的类型而言),如果被调用时得到的参数不满足需要,它将无法返回一个正确的结果。在这种情况下可以考虑报错(后面介绍),也可以考虑让函数返回一个特殊值。不同的处理方式各有优缺点。
本章中出现了几种处理方法:求阶乘函数对于负的参数都返回0;计算三角形面积的函数,对于不能构成三角形三边的参数,返回float("nan")表达式的值。后面还会看到一些情况,这些做法都可以参考。
本章介绍的Python关键字
这里列出本章讨论过的Python关键字,供读者参考:from,import,and,or,not,True,False,if,else,elif,for,in,while,break,continue,def,return,None,pass。在总共33个关键字中,本章已经介绍了19个。包括:

  • 3个特殊字面量,表示逻辑常量的True和False,以及特殊值None;
  • 3个逻辑运算符and、or和not(not还有其他使用方式,见后面的介绍);
  • 流程控制结构中用6个,if,else,elif、for、in、while;
  • 专门语句用4个:break,continue,return,pass;
  • 程序包导入语句用from和import;
  • 函数定义def。
时间: 2024-08-04 03:09:24

《从问题到程序:用Python学编程和计算》——2.11 补充材料的相关文章

《从问题到程序:用Python学编程和计算》——第1章 程序设计和Python 1.1 计算机和程序

第1章 程序设计和Python 我们已经生活在信息时代,环顾四周,信息技术的影响无处不在.由于信息科学技术的发展和应用,我们的世界的方方面面都与20年前大不相同了,例如: 个人生活:看看人们在每天生活中做的各种事情,有多少是在与屏幕键盘(可能是触摸屏)交互,这些都是20年前没有的事情. 人际交流:20年前的人际交流方式很简单.除面对面交流外,只能通过纸笔写信或长途电话(要找专门的电话或者到电话局).今天人手一部手机,可以通过电话.短信.各种网络即时消息相互交流.电子邮件也是私人之间的交流媒介,而

《从问题到程序:用Python学编程和计算》——导读

前 言 计算机诞生至今不过六七十年,但它已经改变了世界,改变了每个人的生活.人们每天都在与计算机交流(如智能手机),各领域专业人员的大量日常工作都需要使用计算机,从事与计算机相关工作的人们已经发展为社会上最大的专业技术社团.计算机的研究和应用.互联网和其他相关领域,还在不断呼唤大量熟悉计算机的专业开发人才.计算机科学技术的开发和应用能力已被广泛认为是国家竞争力的重要组成部分.因此,学习计算机科学技术知识,不仅是社会发展的需要,而且已成为个人的重要职业竞争力.然而,要深入理解计算和计算机,使其成为

《从问题到程序:用Python学编程和计算》——1.2 Python语言简介

1.2 Python语言简介 本节将首先简单介绍Python语言的一些基本情况,包括其发展和使用的情况.而后介绍Python语言系统的安装和使用方面的基本常识.1.2.1 Python语言的发展和应用 Python语言是CWI(荷兰国家数学和计算机研究中心)的程序员Guido van Rossum在1989年开始开发的一种高级编程语言,当时的主要设计目标是希望能用于方便地管理CWI的Amoeba操作系统.后来,由于其各方面的优点而逐渐流行起来. Python语言现在由Python软件基金会(Py

《从问题到程序:用Python学编程和计算》——2.8 重复计算和循环

2.8 重复计算和循环 在前面几节,我们首先看到如何通过语句的顺序组合构造最简单的程序,这种程序是直线型程序,就是简单的一系列语句.这样的程序中只有一条执行路径(一种可能执行方式):Python解释器顺序执行程序里的语句,每个语句执行一次,当语句序列中最后一条语句的执行结束时,整个程序的执行就结束了. 增加了if复合语句,能写出的程序更多,程序的形式也更丰富,其中出现了选择和分支.这样得到的程序可称为分支程序.在分支程序里,每条基本语句最多执行一次,如果实际条件导致的执行没进入某个分支,该分支里

《从问题到程序:用Python学编程和计算》——3.4 定义函数

3.4 定义函数 在最简单的程序中,可能只用到表达式.语句和几种控制结构.但是,仅限于这些基本机制,很难写出很长的解决复杂问题的程序.随着遇到的问题更复杂,我们必须组织好程序的结构,在语句层面之上的基本结构就是函数.一个函数包装起一段代码并给予命名,引进参数将其通用化.定义好的函数可以通过调用表达式使用,非常方便.学习编程的重要一步就是学习定义函数:理解为什么需要定义函数,学会识别编程中定义函数的需求,掌握正确定义函数的技术.本小节和下一章将集中讨论这个问题.3.4.1 为什么定义函数 实际中需

《从问题到程序:用Python学编程和计算》——第3章 基本编程技术 3.1 循环程序设计

第3章 基本编程技术 第2章讨论了简单的计算和编程,展示了一些实例.通过对有关内容的学习,读者应该已经做了一些简单程序,对写程序和做计算有了些实际体会.虽然编程中细节较多,但也是很有趣的工作.为了完成一个程序,首先要分析问题.寻找解决方案,这些需要发挥人的聪明才智和想象力,也可能涉及一些相关领域的知识.要把设计变成可以运行的程序,既需要智力,也需要有条理的工作,一个小错误就可能使程序不能正确执行.当然,高度精确性也是现代社会对人的基本要求,写程序的过程能给我们许多有益的经验. 学习编程要经历一个

《从问题到程序:用Python学编程和计算》——1.3 程序开发

1.3 程序开发 在用Python学习编程时,自然需要了解Python语言,但更重要的是学习.理解和运用人们长期程序设计工作总结出的经验,包括正确的思考问题方法.正确的程序开发方法以及一些有益的常规做法,还要养成良好的编程习惯.随着学习的深入,需要解决的问题也会变得越来越复杂(当然,实际中的问题和解决它们的程序更复杂得多).比较复杂的东西不是随随便便就能做好的,需要认真工作,也需要正确的工作方法.本书中许多地方提出了这些方面的建议,希望引起读者的重视. 本节简单讨论程序的开发过程,包括程序的设计

《从问题到程序:用Python学编程和计算》——2.5 标识符、变量和赋值

2.5 标识符.变量和赋值 前面用表达式做的计算都是各自独立的,实际上是把Python用作一个简单计算器.在提示符下输入一个合法的表达式,解释器处理该表达式,得到一个结果.不同表达式的计算相互无关.显然,这种方式很有局限性,只能完成最简单的计算工作.复杂的计算可能需要经过许多步骤,每步做一点计算工作并记录得到的结果,再基于已得到的结果一步步继续工作下去.要实现这种计算方式,就要有记录计算结果的方法. 2.5.1 变量.名字和值 Python中记录计算结果的机制称为变量.一个变量有一个名字,在程序

《从问题到程序:用Python学编程和计算》——2.6 简单脚本程序

2.6 简单脚本程序 一个Python程序(脚本)是一个独立的文件,文件的扩展名用py,文件的内容应该是一些Python命令(语句).把这种脚本送给Python解释器,令其执行,就能看到执行的效果.本节介绍脚本的建立和执行,以及程序在运行中与人交换信息的问题.[ 实际上,完全可以用任何文本编辑器,所有功能强大的Python程序开发环境也都提供了编辑Python程序的功能.可以根据自己的需要和考虑自行选择.但下面只考虑用IDLE编辑的问题.] 2.6.1 脚本的编辑和执行 一个Python脚本的内