《从问题到程序:用Python学编程和计算》——第2章 计算和编程初步 2.1 数值表达式和算术

简介:

本节书摘来自华章计算机《从问题到程序:用Python学编程和计算》一书中的第2章,第2.1节,作者 裘宗燕,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

第2章

计算和编程初步

写程序是为了实现所需要的计算,计算中总需要处理数据,因此写程序时必然要涉及数据的描述,以及从数据出发的计算过程描述。

人们在学数学时已经写过许多数学表达式,其中的一些表达式描述的就是从一些数值出发的计算过程,例如下面这个数学表达式:

image

这一表达式描述的是从一些数值出发,通过三角函数和各种算术运算,要求算出一个结果。复杂的计算需要用计算机完成,因此在用Python(或其他语言)写程序时,也经常需要写这种计算描述。上面数学表达式对应的Python语言描述是:

5.17 + 1.7 * (2.36 + sin(0.37)) / (12.3**2 – cos(15.4))

这样一段描述称为一个Python表达式,是线性的字符序列。与数学表达式相比,它多了一个作用:可以指挥计算机完成计算,算出一个结果。

学习用Python编程序,第一步是学习表达式的描述方式,弄清如何正确写出所需要的表达式。为此需要了解基本数据和表达式的写法规定:可以包含哪些成分?各种成分的意义是什么?怎样组合?表示怎样的计算过程?本章首先介绍这方面情况。

Python的基本程序单位是命令(或称语句),表达式是命令的组成部分。最基本的命令称为赋值,要求把表达式算出的值保存起来供后面所用。还有另外一些基本命令,后面将逐步介绍。任何复杂一点的计算都需要通过许多命令才能完成,而且需要根据实际情况,控制这些命令的执行过程。Python为此提供了一套控制结构。进一步说,随着程序变得更复杂,平铺直叙的程序将越来越难写好。为此Python引入了一种称为函数的基本抽象机制,使我们可以把一段计算描述包装成一个整体,抽象为一个概念。

本章首先介绍基本的数据描述方式,说明如何描述简单的计算过程。读者将接触到计算领域的许多重要概念,看到它们的地位和作用。而后介绍语句和语句的执行控制,随后介绍函数的概念。讨论中给出了许多程序示例,供读者参考。

2.1 数值表达式和算术

要了解Python的基本计算功能,最好的开始方式就是看一些简单计算实例。

2.1.1 整数计算

读者在第1章已经看到过最简单的Python程序。前面说过,在启动了IDLE的程序执行窗口,或者Python的命令行窗口之后,就会看到提示符“>>>”。我们的输入将出现在它后面。假定输入1之后按压Enter键,就能看到解释器完成的计算:

>>> 1
1

注意,解释器并不是把前一行里人的输入直接拷贝到下面一行,而是完成了第1章说过的三步工作:读入Python程序代码单元(这里单元的内容就是一个字符1),完成程序要求的计算(这里要求解释器计算出1的值,得到结果还是1),最后,解释器在随后的行中显示计算结果。换句话说,这里输入的表达式1就是要求解释器工作的命令,解释器执行这个命令的效果是把计算得到的结果1显示出来。下面的命令能更好地表现解释器实现的计算过程,这里送给解释器的是“1 + 2”,解释器给出计算结果是3。

>>> 1 + 2
3

可见,解释器的工作确实不是简单拷贝。

在上面两个例子里,我们送给解释器由一个或几个字符构成的序列,它们就是最简单的Python程序(结构),称为表达式。表达式也是一种命令,它要求解释器完成一个计算,得到的计算结果称为该表达式的值。在交互式执行方式下,解释器将计算出表达式的值,然后把这个值输出在窗口里,如上所示。解释器计算表达式的操作也常说成是对表达式求值,或者求值该表达式。为了更清楚,在表示交互式计算的情况时,我们将一直用正体表示人的输入(出现在提示符之后),用斜体表示解释器给出的计算结果。

并不是任何数字序列都是合法的Python程序单元,例如:

>>> 01
SyntaxError: invalid token

这里用粗体表示解释器给出的错误信息,IDLE实际上用特殊颜色输出,说明计算中出现了错误。这里的错误是语法错误(SyntaxError),冒号后的细节解释说“01不是形式合法单词(token)”。这个例子说明Python对整数的形式有规定,一般而言,Python对具体单词(单词类别)都有明确的书写形式要求,初学者首先要关注这方面的情况。

我们通过键盘输入一串字符(包括空格等),Python解释器将把它切分成一系列的基本语法成分,称为单词(token)。例如“1 + 2”包含三个单词“1”、“+”和“2”。了解这个基本情况,有助于理解Python系统对程序的处理。

整数和算术运算符

Python能处理的基本数据类别之一是整数。整数常用十进制数字的序列表示,但除非是0,否则数字序列的第一个数字不能是0(上面出错的输入,原因就在这里)。

在Python里可以写任意长(任意大)的整数,例如:

>>> 1234567890123456789012345678901234567890
1234567890123456789012345678901234567890

计算机硬件只能处理固定长度的整数。例如,在目前常见的64位计算机里,硬件指令一般能处理用64位二进制编码表示的整数,其范围为-263~263-1,也就是-9223372036854775808到9223372036854775807。在Python系统里对整数没有数值限制,是通过复杂的软件技术实现的。当然,由于计算机都是规模有穷的物理设备,其资源有限,无论采用什么样的软件技术,也只能在计算机硬件的物理限制内工作。实际Python系统能表达的整数还是有穷的,但这个范围非常大,一般的使用都不会超出其实际限制。但在另一方面,计算中涉及的整数越大,计算也会越慢。读者可以通过试验看到这种情况。

整数的基本计算用算术运算符描述,下面6个算术运算符可用于整数:

image

其中的加号既作为二元加法运算符,也作为正号;减号既作为二元减法运算符,也作为负号。其余几个都是二元运算符。这里有两个除法运算符,其中 // 表示整除,得到商的整数部分,对两个整数应用 / 得到的不是整数,而是下节将要介绍的浮点数。% 运算符求整除的余数,** 求整数的乘幂。从下面几个例子中可以看到一些情况:

>>> 30 / 7
4.285714285714286
>>> 30 // 7
4
>>> -30 // 7
-5
>>> 30 % 7
2
>>> -30 % 7
5
>>> 30 // -7
-5
>>> 2**10
1024

第一个表达式的结果是带小数点的数,这就是所谓的浮点数。从上面例子还可以看到负数的整除和求余数规则,无论正整数或负整数,解释器都保证有:

被除数=除数×商+余数

整数运算可能得到很大的结果,例如:

>>> 2**1000
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376

结果是一个超过300位的整数,输出了多行。这是准确的结果。

在Python语言里写算术表达式时,人们提倡在一般运算符的前后各加一个空格,但在正负号和后面数字之间不留空格,乘方运算符前后也不留空格。当然,这里留不留空格,表达式的语义不变,提倡留空格的形式,只是为了使写出的表达式更清晰。

运算符和表达式计算

Python允许写任意复杂的算术表达式,只要其形式符合要求,运算符正确应用于运算对象。表达式里出现多个运算符,就有计算的顺序问题。这里的规则与数学中类似:先乘方,再乘除(包括求余数),最后加减,也允许用括号规定计算顺序。例如:

>>> 3**2 + 4 * 5
29
>>> (3**2 + 4) * 5
65

底数前面的正负号作用在乘方之后,其他情况下正负号先起作用。如:

>>> -3**4
-81
>>> -5 * -3
15

多个乘除或多个加减运算符在表达式中顺序出现时,总是从左到右顺序计算。而多个乘幂运算符顺序出现,则先做右边的乘幂。例如:

>>> 10 - 4 - 3 + 5
8
>>> 2**3**2
512

第2个例子求的是2的9次幂,而不是8的二次方。

基于多个二元运算符,通过组合可以写出任意复杂的表达式。在这种情况下,Python语言必须做出严格的规定,保证每个合法表达式的计算过程都有严格定义的唯一顺序。这种规定一般包括几个方面:

1)把运算符分类,给每个类别的运算符规定一个优先级。属于不同类别的运算符在表达式里顺序出现时,优先级高的运算符先做运算。在Python语言里,乘幂运算符的优先级高于一元运算符(只有一个运算对象)正号和负号,正负号的优先级高于乘除,乘除运算符的优先级高于加减。

2)给每类运算符规定一种结合顺序,从左到右或从右到左。当属于同一优先级的运算符在表达式里顺序出现时,就按其结合顺序决定计算的顺序。如前所述,在Python里加减运算符和乘除运算符都采用从左到右的结合顺序,乘幂运算符则采用从右到左的结合顺序。一元运算符只有一个运算对象,也采用从右到左的结合顺序。例如,在Python里可以写 -------20,虽然这样写的意义不大,但也是合法表达式。

3)提供括号,用于明确描述特定计算顺序。注意,一般的程序语言都只用圆括号作为描述运算顺序的括号,其他括号另有它用。Python也是这样。

4)如果一个运算符有多个运算对象,可能需要规定几个运算对象的计算顺序。Python规定多个运算对象从左到右顺序地一个个计算,例如,对于表达式(3 + 5) * (7 – 2),先算出(3 + 5),再算出(7 – 2),最后使用乘法。

最后一条有点奇怪。对于从基本数据(如整数)出发写出的算术表达式,先计算运算符的哪个运算对象,得到的结果都一样。但是,确实存在另外一些Python表达式,它们的情况可能有所不同。在后面章节里可以看到这种规定的意义。

前面说过,Python解释器首先把程序看作是一系列单词。在前面程序实例中,我们已经看到了一些不同的单词,如:整数、左右括号、算术运算符。其中有两个算术运算符的形式比较特殊,整除运算符由连续的两个斜线符构成,乘幂运算符由两个连续的星号构成。这种运算符称为组合运算符,它们的两个字符之间不能有空格。
整数也很特殊:满足要求的一串数字构成一个整数,它本身又代表了计算中的一项数据,是一个量。具有这种性质的单词称为字面量,意指直接写出的数据。

各种单词都有严格规定的写法,但是,即使一串单词中每一个的形式都合法,整个单词序列也未必构成一个合法的表达式。例如:

>>> 23 45
SyntaxError: invalid syntax
>>> 23 * * 3 / / 5
SyntaxError: invalid syntax

这里出现了表达式(或其他Python程序结构)构成方式的错误,称为语法(syntax)错误。解释器在处理程序的过程中发现语法错,可以明确给出发现错误的位置。在IDLE里,系统会用特殊的颜色标明这个位置。如第1章中的介绍,应该从这个位置出发检查程序,找到错误的根源并修正之。上面第一个例子可能是忘记写运算符,第二个例子可能是组合运算符的两个字符之间多了空格。具体怎么更正由写程序的人确定。

语法错误是程序中的结构描述错误,说明程序中某个片段的写法不符合Python语言的要求。不符合Python语法的字符序列不是Python程序,因此不能执行。算术表达式是Python语言中的一种结构,下面还要介绍Python语言的其他结构。每种结构都有规定的语法形式,要在程序里使用某种结构,就必须按正确的语法形式写出它们。

还请注意,即使表达式的形式正确,计算中也可能出错。例如:

>>> 12 / 0
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    12 / 0
ZeroDivisionError: division by zero

最后一行是解释器说明具体错误的信息:ZeroDivisionError意为计算中出现除以0的错误。倒数第二行给出了发生错误的表达式,前面两行是定位信息。这个错误属于第1章说明的动态运行错误。动态错误还有很多,后面会看到。

长表达式

Python允许写任意长的表达式,但原则上说一个表达式应该写在一行里。在交互式计算的环境中,一旦输入换行符号(用Enter键),解释器就认为表达式结束了,就会去检查它的形式是否合法。如果语法正确,解释器就会求值这个表达式。

假设我们计划输入一个很长的表达式,但为了某种需要(例如,太长了看不清楚),希望在其中一些地方断行。这时就遇到了问题:一旦输入一个换行符号,Python解释器就试图去求值所给的输入。如果这行输入恰好满足表达式的语法,解释器就会把它看作完整的表达式,求出它的值输出。如果这行的内容不是一个正确的表达式,解释器就输出错误信息。无论如何,这两种情况可能都不是我们期望的。

为满足人们的需要,Python做出了特殊的规定。存在两种情况,解释器将认为当前表达式没结束,其内容延续到下一行,将把下一行的内容作为本行的继续:

1)在读入一行遇到换行符时存在明显的未完成结构。以算术表达式为例,如果一行中出现的左括号没有匹配的右括号,解释器就继续读入下一行作为本行的续行。

2)一行最后一个字符(换行符前的那个字符)是反斜线符号 ,这时无论当前行的内容如何,解释器都把下一行作为续行。需要注意,在反斜线符和换行符之间不能有任何字符,包括不能有空格。一行最后的反斜线符号也称为续行符。

允许利用这两条规则反复续行。了解了这些情况,我们就可以写出任意长的表达式,可以根据自己的想法和需要,把一个复杂表达式写在任意多个连续的行中。

Python对于格式的上述要求(一个结构部分应该写在一行里)和续行规定(上面两条规则)是普遍有效的,对于任何程序结构都有效。

2.1.2 浮点数和复数

Python用浮点数模拟数学中的实数,通过浮点数计算模拟实数计算。当然,数学的实数一般不能表示为有穷长度的数字序列(例如是无理数),而Python浮点数是有穷表示,因此只能表示一定范围里的数,而且精度有限,只能表示一些实数的近似值。

浮点数字面量、溢出和截断

CPython(也就是说,Python软件基金会发布的官方的语言解释器)直接使用计算机硬件支持的标准浮点数。常见硬件都采用IEEE754浮点数标准,标准浮点数具有16~17位十进制精度,表示范围大致为,绝对值太小的实数将归结为0,绝对值更大的实数也无法表示。这些情况与前面介绍的整数不同。更详细的情况,可参看本章最后“语言细节”一节的内容。

浮点数字面量用十进制数字的序列表示,但描述中必须或者包括一个表示小数点的圆点,或者包括一个指数部分。例如:

>>> 3.14259265
3.14259265
>>> 5e-324
5e-324
>>> 1.7e308
1.7e+308
>>> 1.8e308
inf

浮点数的基本部分表示其数值,这部分称为尾数。字符e之后的部分称为指数,表示数的量级。表示指数的字符可以用e或者E,随后是一个可以包含正负号的整数指数。几个部分之间必须顺序相连,不能有空格。上面最后的例子说明,过大的浮点数在Python里无法表示,系统并不认为出错,而是给出inf表示这种情况(正无穷)。浮点数字面量,或者浮点数计算的结果,都可能超出可表示范围,这种情况称为浮点数溢出。

注意,对于同一个浮点数,可以用多种不同方式去描述它,例如,1234.0、1.234e3、0.1234e4是写法不同的三个浮点数字面量,而它们描述的是同一个浮点数,并不因为在字面量写法上的差异而有任何不同。

在显示计算结果时,解释器会自动选择合适的方式,尽可能使输出容易阅读:

>>> 1.3e5
130000.0
>>> 12345678901234567890.0
1.2345678901234567e+19

如果需要表示的实数的精度太高,Python浮点数无法满足需要(例如上面第二个例子,字面量包含20位有效数字),解释器就会做适当的截断和舍入。细心的读者可能看到上面第二个浮点字面量的输出,奇怪结果的最后一位为什么是7而不是8。从本章最后的补充阅读材料里可以看到一些端倪。

浮点数计算和误差

对于浮点数,也可以做前面给出的各种算术运算,可以写出任意复杂的计算表达式,描述时同样要注意算术运算符的优先级、结合顺序,也可以使用括号。例如:

>>> 1.25 ** 3.4
2.135472800670465
>>> 27.86 // 2.34
11.0
>>> 27.86 % 3.4
0.6600000000000001

比较这里的最后两个例子,从中可以看到了一个现象:浮点数的计算不准确,有误差。对最后一个例子,正确无误的结果是0.66,系统给出的却是0.6600000000000001。浮点数计算有误差是计算中必然出现的一种现象。下面是另一个例子:

>>> 1.2**20
38.33759992447472
>>> 12**20 / 10**20
38.33759992447475

在数学上,这里计算的两个表达式等价,但在Python系统里却得到了不同结果。这一情况并不说明Python系统有错,而是计算机硬件浮点数表示和计算方式的必然结果:由于浮点数的精度有限,在反复计算中需要不断舍入,累积的误差可能会越来越大。采用不同计算方式,产生的累积误差可能不同,因此造成上面的现象。

基于浮点数的计算也称为数值计算,数值计算中会出现计算误差,因此是一种近似计算,得到的结果是不精确的。最早开发计算机,就是为了解决复杂的科学与工程问题,其中常涉及非常复杂繁琐的数值计算。在很长的计算过程中,误差积累有可能变得非常大,甚至使最终得到的结果毫无意义。控制和评估数值计算中的误差,是科学与工程计算中的重要问题,既是非常重要的理论研究课题,也具有重要的实际意义。

复数

Python也支持复数的表示和复数计算。在数学里复数通常写成,其中a和b均为实数,分别称为这个复数的实部和虚部。Python中的复数表示与此类似,但用字符j或者J作为虚部的符号,例如1 + 2j、1.2 + 2.7J。

复数用一对浮点数表示,两个浮点数分别表示其实部和虚部。例如:

>>> (1 + 2j)**10
(237-3116j)
>>> (1 + 2j)**100
(-6.443164690985892e+34-6.113241307762508e+34j)

可以看到,解释器输出复数时总用一对括号将其括起。基本四则运算和乘方都能作用于复数,但整除和求余数运算对复数无定义。

相关文章
|
14天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
14天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
1天前
|
SQL 关系型数据库 MySQL
第十三章 Python数据库编程
第十三章 Python数据库编程
|
1天前
|
存储 索引 Python
Python从入门到精通——1.3.1练习编写简单程序
Python从入门到精通——1.3.1练习编写简单程序
|
1天前
|
存储 网络协议 关系型数据库
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
|
6天前
|
数据采集 JavaScript 前端开发
使用Python打造爬虫程序之破茧而出:Python爬虫遭遇反爬虫机制及应对策略
【4月更文挑战第19天】本文探讨了Python爬虫应对反爬虫机制的策略。常见的反爬虫机制包括User-Agent检测、IP限制、动态加载内容、验证码验证和Cookie跟踪。应对策略包括设置合理User-Agent、使用代理IP、处理动态加载内容、验证码识别及维护Cookie。此外,还提到高级策略如降低请求频率、模拟人类行为、分布式爬虫和学习网站规则。开发者需不断学习新策略,同时遵守规则和法律法规,确保爬虫的稳定性和合法性。
|
7天前
|
安全 数据处理 开发者
《Python 简易速速上手小册》第7章:高级 Python 编程(2024 最新版)
《Python 简易速速上手小册》第7章:高级 Python 编程(2024 最新版)
19 1
|
7天前
|
人工智能 数据挖掘 程序员
《Python 简易速速上手小册》第1章:Python 编程入门(2024 最新版)
《Python 简易速速上手小册》第1章:Python 编程入门(2024 最新版)
35 0
|
7天前
|
SQL 安全 Go
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
在Python Web开发中,确保应用安全至关重要,主要防范SQL注入、XSS和CSRF攻击。措施包括:使用参数化查询或ORM防止SQL注入;过滤与转义用户输入抵御XSS;添加CSRF令牌抵挡CSRF;启用HTTPS保障数据传输安全;实现强身份验证和授权系统;智能处理错误信息;定期更新及审计以修复漏洞;严格输入验证;并培训开发者提升安全意识。持续关注和改进是保证安全的关键。
16 0
|
8天前
|
Python Serverless API
Python风险价值计算投资组合VaR、期望损失ES
Python风险价值计算投资组合VaR、期望损失ES
23 0
Python风险价值计算投资组合VaR、期望损失ES