Python基础及应用
上QQ阅读APP看书,第一时间看更新

2.3 基本计算

2.3.1 变量

在程序中,我们需要保存一些值或者状态之后再使用,这种情况就需要用一个变量来存储它,这个概念跟数学中的“变量”非常类似,比如下面一段代码:

在36行按〈Enter〉键后并不会出现新的一行,而是光标在最左端闪动等待用户输入,我们输入任意内容,比如Type something here,按〈Enter〉键后才会出现新的一行,这时候a中就存储了我们输入的内容。显然,根据输入内容的不同,a的值也是不同的,所以说a是一个变量。

要注意的是,在编程语言中单个等号“=”一般不表示“相等”的语义,而是表示“赋值”的语义,即把等号右边的值赋给等号左边的变量,后面讲解运算符的时候会有更加详细的解释。

在Python中声明一个变量是非常简单的事情,如果变量的名字之前没有被声明过的话,只要直接赋值就可以声明新变量了,比如:

考虑下面这段代码:

注意到了吗?a的类型是在不断变化的,这也是Python的特点之一——动态类型,即变量的类型可以随着赋值而改变,这样很符合直觉,同时也易于程序的编写。

变量的名称叫作标识符,而开发者可以近乎自由地为变量取名。之所以说是“近乎”自由,是因为Python的变量命名还是有一些基本规则的。

● 标识符必须由字母、数字、下划线构成。

● 标识符不能以数字、开头。

● 标识符不能是Python关键字。

什么是关键字呢?关键字也叫保留字,是编程语言预留给一些特定功能的专有名字。Python具体的关键字列表如下:

这些关键字的具体功能会在后续章节覆盖到,比如马上就会遇到True、False、and、or、not这几个关键字。

2.3.2 算术运算符

运算符用于执行运算,运算的对象叫操作数。比如对于“+”运算符,在表达式1+2中,操作数就是1和2。运算符根据操作数的数量不同有一元运算符、二元运算符和三元运算符。在Python中,根据功能还可分为算术运算符、比较运算符、赋值运算符、逻辑运算符、位运算符、成员运算符、身份运算符。其中算术运算符、比较运算符、赋值运算符、逻辑运算符和位运算符比较基础也比较常用。而剩下两种,成员运算符和身份运算符,则需要一些前置知识才方便理解,将在后面的章节认识它们。

Python除了支持之前提到的四则运算,它还支持取余、乘方、取整除这三种运算。这些运算都是二元运算符,也就是说它们需要接受两个操作数,然后返回一个运算结果。

为了方便举例,我们定义两个变量,alice=9bob=4,具体的运算规则如表2-1所示。

表2-1 算术运算符

值得注意的是,通过duck typing其实可以让上述运算符支持任意两个对象之间的运算,这是Python中很重要的一种特性,我们会在面向对象编程中提到它,这里简单理解为算术运算符只用于数字类型运算就可以了。

特殊的,+和-还是两个一元运算符,例如-alice可以获得alice的相反数。

1.比较运算符和逻辑运算符

比较运算符,顾名思义,是将两个表达式的返回值进行比较,返回一个布尔型变量。它也是二元运算符,因为需要两个操作数才能产生比较。

逻辑运算符,是布尔代数中最基本的三个操作,也就是与、或、非,比如:

要注意的是,这些表达式最后输出的值只有两种—— TrueFalse,这跟之前介绍的布尔型变量取值只有两种是完全吻合的。其实与其理解为两种取值,不如理解为两种逻辑状态,即一个命题总有一个值,真或者假。

所有的比较运算符运算规则如表2-2所示。

表2-2 比较运算符

注意这里正如之前提到的,单个等号的语义为“赋值”,而两个等号放一起的语义才是“相等”。

但是如果我们想同时判断多个条件,那么这时候就需要逻辑运算符了,比如:

通过逻辑运算符,我们可以连接任意个表达式进行逻辑运算,然后得出一个布尔类型的值。

逻辑运算符的只有and,or和not,具体的运算规则如表2-3所示。

表2-3 逻辑运算符

2.赋值运算符

二元运算符中最常用的就是赋值运算符“=”,它的意思是把等号右边表达式的值赋值给左边的变量,当然要注意这么做的前提是赋值运算符的左值必须是可以修改的变量。如果我们赋值给了不可修改的量,就会产生如下的错误:

对一个字面量或者关键词进行赋值操作,这显然是没有意义并且不合理的,所以它报错的类型是SyntaxError,意思是语法错误。这里是我们第一次接触到了Python的异常机制,后面的章节会更加详细地介绍,因为这是写出一个强鲁棒性程序的关键。

3.复合赋值运算符

很多时候操作数本身就是赋值对象,比如i=i+1。由于这样的语句会经常出现,所以为了方便和简洁,就有了算术运算符和赋值运算符相结合的复合赋值运算符。它们相当于将一个变量本身作为左侧的操作数,然后将相关的运算结果赋给本身。

算术运算符对应的复合赋值运算符,如表2-4所示。

表2-4 复合赋值运算符

(续)

我们来动手试一试复合赋值运算符,代码如下:

可以看到复合赋值运算符的确简化了代码,同时也增强了可读性。

4.位运算符

所有的数值类型在计算机中都是二进制存储的,比如对于一个整数30而言,在计算机内的存储形式可能就是0011110,而位运算就是以二进制位为操作数的运算。

所有的位运算符如表2-5所示。

表2-5 位运算符

位运算比较抽象,下面举例说明。

(1)移位运算

先看按位左移和右移,代码如下:

a是一个十进制表示为211,二进制表示为11010011的整数,我们对它进行左移1位,得到了422。不难发现,这就是乘以2。从二进制的角度来看,就是在这个数最后加了个0,但是从位运算的角度看,实际的操作是所有的比特位全都向左移动了一位,而新增的最后一位用0补上。这里要注意的是,移位运算符的右操作数是移动的位数。

我们用一个表来精细对比下前后的二进制表示,其中表的第一行是二进制表示的位数,低位在右边,高位在左边,如表2-6所示。

表2-6 按位左移

对于左移而言,所有的二进制位会向左移动数位,空出来的位用0补齐。如果丢弃的位中没有1,也就是说没有溢出的话,等价于原来的数乘以2。

类似地,右移就是丢弃最后几位,剩下的位向右移动,空出来的位使用0补齐。从十进制的角度来看,这就是整除以2,如表2-7所示。

表2-7 按位右移

(2)与运算

先看一个例子:

这里给出与运算的运算规则,在离散数学中这也叫真值表,如表2-8所示。

表2-8 与运算真值表

对于与运算的规则其实非常好理解,只要参与运算的两个二进制位中任意一位为0那么结果就是0,是不是觉得和之前讲的逻辑运算符and有点像?实际上从逻辑运算的角度来看,二者就是等价的。

直接看可能与运算有些难理解,我们用一个表格来说明,如表2-9所示。

表2-9 与运算

从低位到高位一位一位地分析刚才这个例子。

● 第0位,1 &0=0

● 第1位,1 &0=0

● 第2位,0 &0=0

● 第3位,0 &0=0

● 第4位,1 &1=1

● 第5位,0 &1=0

● 第6位,1 &0=0

● 第7位,1 &0=0

所以就得到了结果00010000。与运算有一个常见的应用就是掩码,比如我们想获得某个整数二进制表示中的前三位,那么就可以把这个整数和7相与,因为7的二进制表示是0b00000111,这样一来结果中除了前三位以外所有的二进制位都是0,而结果中前三位和原来前三位是一样的,也就是说利用与运算可以获得一个整数二进制表示中的任何一位,这就是“掩码”的作用。

(3)或运算

仍然是先看一个例子:

这里给出或运算的规则,如表2-10所示。

表2-10 或运算真值表

对于或运算来说,参与运算的两个二进制位只要有一个为1结果就为1,这跟之前讲过的or运算符是一致的。

我们再用表格分析上述或运算,如表2-11所示。

表2-11 或运算

从低位到高位一位一位地分析这个例子。

● 第0位,1|0=1

● 第1位,1|0=1

● 第2位,0|0=0

● 第3位,0|0=0

● 第4位,1|1=1

● 第5位,0|1=1

● 第6位,1|0=1

● 第7位,1|0=1

所以就得到了结果11110011。或运算可以用来快速把二进制中某些位置1,比如我们想把某个数的前三位置1,只要跟7或运算即可,因为7的二进制表示是0b00000111,可以确保前三位运算的结果一定是1,而其他位和原来一致。

5.按位取反

按位取反是一个一元运算符,因为它只有一个操作数,它的用法如下:

按位取反的运算规则,如表2-12所示。

表2-12 按位取反真值表

也就是每一位如果是0就变成1,如果是1就变成0。按照这个运算规则,运算的结果应该如表2-13所示。

表2-13 按位取反

但是上面的例子中按位取反后的二进制表示有点奇怪,它竟然有一个负号,而且也跟上面表格中的结果不太一样,问题出在哪了呢?

我们回想一下,计算机内所有数据都是以二进制存储的,负号也是一样,为了处理数据方便,计算机采用了一种叫作“补码”的方法来存储负数,具体的做法是二进制表示的最高位为符号位,0表示正数,1表示负数,对于一个用补码表示的二进制整数wn-1wn-2...w1,它的实际数值为

这看起来非常抽象,为了方便叙述回到上面这个例子,对于211来说,因为Python输出二进制的时候省略了符号位,只用正负号表示,所以它的二进制表示其实应该是011010011,按照上述给的公式计算的话就是27+26+24+21+20=221,接着按照取反的运算规则会得到100101100,同样按照公式计算的话有-28+25+23+22=-212,结果和例子中是一样的。

所以就本例而言,取反得到的负数在计算机内的存储形式的确是100101100。但是由于Python输出二进制的时候没有符号位,只有正负号,也就是说如果原样输出0b100101100,最高位1其实不是符号位,实际表示的是正数0100101100(这里最高位0表示正数),这是不合理的,所以Python输出的是-0b11010100,因为0b11010100表示的整数是011010100,即212。

6.异或运算

仍然是先看一个例子:

异或的具体规则如表2-14所示。

表2-14 异或运算真值表

异或的运算规则是参与运算的两个二进制位相异(不同)则为1,相同则为0。

再用表格分析上述异或运算,如表2-15所示。

表2-15 异或运算

从低位到高位一位一位地分析:

● 第0位,1 ^0=1

● 第1位,1 ^0=1

● 第2位,0 ^0=0

● 第3位,0 ^0=0

● 第4位,1 ^1=0

● 第5位,0 ^1=1

● 第6位,1 ^0=1

● 第7位,1 ^0=1

所以就得到了结果0b11100011。

7.复合赋值运算符

位运算也有相应的复合赋值运算符,如表2-16所示。

表2-16 位运算对应的复合赋值运算符

2.3.3 运算符优先级

Python中不同的运算符具有不同的优先级,高优先级的运算符会优先于低优先级的运算符计算,比如乘号的优先级应该比加号高,幂运算的优先级应该比乘法高,看一个简单的例子:

但是Python的运算符远不止加减乘除几个,表2-17中按照优先级从高到低列出了常用的运算符。其中的is表示内存地址的一致,is not表示内存地址的不一致,in表示某个元素在列表中,not in表示某个元素不在列表中。

表2-17 运算符优先级

如果需要改变优先级,可以通过圆括号( )来提升优先级。( )优先于一切运算符号,程序会优先运算最内层的( )的表达式。