2.2 计算机记性好
1.变量革命
上面的运算中出现的数据,无论是1和5.2这样的数值,还是True和False这样的布尔值,都会在运算结束后消失。有时,我们想把数据存储到存储器中,以便在后面的程序中重复使用。计算机存储器中的每个存储单元都有一个地址,就像是门牌号。我们可以把数据存入特定门牌号的隔间,然后通过门牌号来提取之前存储的数据。
但用内存地址来为存储的地址建索引,其实并不方便:
- 内存地址相当冗长,难以记忆。
- 每个地址对应的存储空间大小固定,难以适应类型多变的数据。
- 对某个地址进行操作前,并不知道该地址的存储空间是否已经被占用。
随着编程语言的发展,开始有了用变量的方式来存储数据。变量和内存地址类似,也起到了索引数据的功能。新建变量时,计算机在空闲的内存中开辟存储空间,用来存储数据。和内存地址不同的是,根据变量的类型,分配的存储空间会有大小变化。程序员给变量起一个变量名,在程序中作为该变量空间的索引。数据交给变量,然后在需要的时候通过变量的名字来提取数据。比如下面的Python程序:
v = "Vivian"
print(v) #打印出"Vivian"
在上面的程序中,我们把数值10交给变量 v 保存,这个过程称为赋值(Assignment)。Python中用等号=来表示赋值。借助赋值,一个变量就建立了。从硬件的角度来看,给变量赋值的过程,就是把数据存入内存的过程。变量就像能装数据的小房间,变量名是门牌号。赋值操作是让某个客人前往该房间。
“让Vivian入住到v字号房。”
在随后的加法运算中,我们通过变量名 v 取出了变量所包含的数据,然后通过print()打印出来。变量名是从内存中找到对应数据的线索。有了存储功能,计算机就不会犯“失忆症”了。
“v字号房住的是谁?”
“是Vivian呀。”
酒店的房间会有不同的客人入住或离开。变量也是如此。我们可以给一个变量赋其他的值。这样,房间里的客人就变了。
v = "Tom"
print(v) #打印出"Tom"
在计算机编程时,经常设置许多变量,让每个变量存储不同功能的数据。例如,电脑游戏中可能记录玩家拥有的不同资源的数目,就可以用不同的变量记录不同的资源。
gold = 100 # 100个金子
wood = 20 # 20个木材
wheat = 29 # 29个小麦
在游戏过程中,可以根据情况增加或者减少某种资源。例如,玩家选择伐木,就增加5个木材。这个时候,就可以对相应变量执行加5的操作。
wood = wood + 5
print(wood) # 打印出25
计算机先执行赋值符号右边的运算。原有的变量值加5,再赋予给同一个变量。在游戏进行的整个过程中,变量 wood 起到了追踪木材数据的作用。玩家的资源数据被妥善地存储起来。
变量名直接参与运算,这是迈向抽象思维的第一步。在数学上,用符号来代替数值的做法称为代数。今天的很多中学生都会列出代数方程,来解决“鸡兔同笼”之类的数学问题。但在古代,代数是相当先进的数学。欧洲人从阿拉伯人那里学到了先进的代数,利用代数的符号系统,摆脱了具体数值的桎梏,更加专注于逻辑和符号之间的关系。在代数的基础上发展出近代数学,为近代的科技爆炸打下了基础。变量也让编程有了更高一层的抽象能力。
变量提供的符号化表达方式,是实现代码复用的第一步。比如之前计算购房所需现金的代码:
860000*(0.15 + 0.2)
当我们同时看多套房时,860000这个价格会不断变动。为了方便,我们可以把程序写成:
total = 860000
requirement = total*(0.15 + 0.2)
print(requirement) # 打印结果301000.0
这样,每次在使用程序时,只需更改860000这个数值就可以了。当然,我们还会在未来看到更多的复用代码的方式。但变量这种用抽象符号代替具体数值的思维,具有代表性的意义。
2.变量的类型
数据可能有很多不同的类型,例如5这样的整数、5.9这样的浮点数、True和False这样的布尔值,还有第1章中见过的字符串"Hello World!"。在Python中,我们可以把各种类型的数据赋予给同一个变量。比如:
var_integer = 5
print(var_integer) # a存储的内容为整数5
var_string = "Hello World!"
print(var_string) # a存储的内容变成字符串"Hello World!"
可以看到,后赋予给变量的值替换了变量原来的值。Python能自由改变变量类型的特征被称为动态类型(Dynamic Typing)。并不是所有的语言都支持动态类型。在静态类型(Static Typing)的语言中,变量有事先说明好的类型。特定类型的数据必须存入特定类型的变量。相比于静态类型,动态类型显得更加灵活便利。
即使是可以自由改变,Python的变量本身还是有类型的。我们可以用type()这一函数来查看变量的类型。比如说:
var_integer = 10
print(type(var_integer))
输出结果是^(1)^{#ch1-back}:
<class 'int'>
int是整数integer的简写。除此之外,还会有浮点数(Float)、字符串(String,简写为str)、布尔值(Boolean,简写为bool)。常见的类型包括:
>>>a = 100 # 整型
>>>a = 100.0 # 浮点型
>>>a = 'abc' # 字符串。也可以使用双引号"abc"标记字符串。
>>>a = True # 布尔值
计算机需要用不同的方式来存储不同的类型。整数可以直接用二进制的数字表示,浮点数却用额外记录小数点的位置。每种数据所需的存储空间也不同。计算机的存储空间以位(bit)为单位,每一位能存储一个0或1的数字。为了记录一个布尔值,我们只需让1代表真值,0代表假值就可以。所以布尔值的存储只需要1位。对于整数4来说,变换成二进制位100。为了存储它,存储空间至少要有3位,分别记录1、0、0。
为了效率和实用性,计算机在内存中必须要分类型存储。静态类型语言中,新建变量必须说明类型,就是这个道理。动态类型的语言看起来不需要说明类型,但其实是把区分类型的工作交给解释器。当我们更改变量的值时,Python解释器也在努力工作,自动分辨出新数据的类型,再为数据开辟相应类型的内存空间。Python解释器贴心的服务让编程更加方便,但也把计算机的一部分能力用于支持动态类型上。这也是Python的速度不如C语言等静态类型语言的一个原因。
3.序列
Python中一些类型的变量,能像一个容器一样,收纳多个数据。本小节讲的序列(Sequence)和下一小节的词典(Dictionary),都是容器型变量。我们先从序列说起。就好像一列排好队的士兵,序列是有顺序的数据集合。序列包含的一个数据被称为序列的一个元素(element)。序列可以包含一个或多个元素,也可以是完全没有任何元素的空序列。
序列有两种,元组(Tuple)和列表(List)。两者的主要区别在于,一旦建立,元组的各个元素不可再变更,而列表元素可以变更。所以,元组看起来就像一种特殊的表,有固定的数据。因此,有的翻译也把元组称为“定值表”。创建元组和表的方式如下:
>>>example_tuple = (2, 1.3, "love", 5.6, 9, 12, False) # 一个元组
>>>example_list= [True, 5, "smile"] # 一个列表
>>>type(example_tuple) # 结果为'tuple'
>>>type(example_list) # 结果为'list'
可以看到,同一个序列可以包含不同类型的元素,这也是Python动态类型的一个体现。还有,序列的元素不仅可以是基本类型的数据,还可以是另外一个序列。
>>>nest_list = [1,[3,4,5]] # 列表中嵌套另一个列表
由于元组不能改变数据,所以很少会建立一个空的元组。而序列可以增加和修改元素,所以Python程序中经常会建立空表:
>>>empty_list = [] # 空列表
既然序列也用于储存数据,那么我们不免要读取序列中的数据。序列中的元素是有序排列,所以可以根据每个元素中的位置来找到对应元素。序列元素的位置索引称为下标(Index)。Python中序列的下标从0开始,即第一个元素的对应下标为0。这一规定有历史原因在里面,是为了和经典的C语言保持一致。我们尝试引用序列中的元素:
>>>example_tuple[0] # 结果为2
>>>example_list[2] # 结果为'smile'
>>>nest_list[1][2] # 结果为5
表的数据可变更,因此可以对单个元素进行赋值。你可以通过下标,来说明想对哪个元素赋予怎样的值:
>>>example_list[1] = 3.0
>>>example_list # 列表第二个元素变成3.0
元组一旦建立就不能改变,所以你不能对元组的元素进行上面的赋值操作。
对于序列来说,除了可以用下标来找到单个元素外,还可以通过范围引用的方式,来找到多个元素。范围引用的基本样式是:
序列名\[下限:上限:步长\]
下限表示起始下标,上限表示结尾下标。在起始下标和结尾下标之间,按照步长的间隔来找到元素。默认的步长为1,也就是下限和上限之间的每1个元素都会出现在结果中。引用的多个元素将成为一个新的序列。下面是一些范围引用的例子。
>>>example_tuple[:5] # 从小标0到下标4,不包括下标5的元素
>>>example_tuple[2:] # 从下标2到最后一个元素
>>>example_tuple[0:5:2] #下标为0,2,4的元素。
>>>sliced = example_tuple[2:0:-1] # 从下标2到下标1
>>>type(sliced) # 范围引用的结果还是一个元组
上面都是用元组的例子,表的范围引用效果完全相同。在范围引用的时候,如果写明上限,那么这个上限下标指向的元素将不包括在结果中。
此外,Python还提供了一种尾部引用的语法,用于引用序列尾部的元素:
>>>example_tuple[-1] # 序列最后一个元素
>>>example_tuple[-3] # 序列倒数第三个元素
>>>example_tuple[1:-1] # 序列的第二个到倒数第二个元素
正如example_tuple[1:-1]这个例子,如果是范围引用,那么上限元素将不包含在结果中。
序列的好处是可以有序地储存一组数据。一些数据本身就有有序性,比如银行提供的房贷利率,每年都会上下浮动。这样的一组数据就可以存储在序列中:
interest_tuple = (0.01, 0.02, 0.03, 0.035, 0.05)
4.词典
词典从很多方面都和表类似。它同样是一个可以容纳多个元素的容器。但词典不是以位置来作为索引的。词典允许用自定义的方式来建立数据的索引:
>>>example_dict = {"tom":11, "sam":57,"lily":100}
>>>type(example_dict) # 结果为'dict'
词典包含有多个元素,每个元素以逗号分隔。词典的元素包含两部分,键(Key)和值(Value)。键是数据的索引,值是数据本身。键和值一一对应。比如上面的例子中,"tom"对应11,"sam"对应57,"lily"对应100。由于键值之间的一一对应关系,所以词典的元素可以通过键来引用。
>>>example_dict["tom"] # 结果为11
在词典中修改或增添一个元素的值:
>>>example_dict["tom"] = 30
>>>example_dict["lilei"] = 99
>>>example_dict #结果为{"tom": 30, "lily": 100, "lilei": 99, "sam": 57}
构建一个新的空的词典:
>>>example_dict = {}
>>>example_dict # 结果为{}
词典不具备序列那样的连续有序性,所以适于存储结构松散的一组数据。比如首付比例和税率可以存在同一个词典中:
rate = {"premium": 0.2, "tax": 0.15}
在词典的例子中,以及大部分的应用场景中,我们都使用字符串来作为词典的键。但其他类型的数据,如数字和布尔值,也可以作为词典的键值。本书将在后面讲解,究竟哪些数据可以作为词典的键值。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论