基本数据类型

我们会从 Python 最基本的内置数据类型开始学习,这些类型包括:

空值 字符串 元组
布尔 字节 字典
数字 列表 集合

请暂且记住这一句话,Python 中所有的东西都是以对象(object)的形式存在的。

请在 Python 的交互环境中跟着输入下文中的代码,在需要的地方会有简单的解释。

注释(comments)

注释是任何存在于 # 号右侧的文字,注释是写给人看的而不是计算机,注释对我们的代码运行没有任何影响。举个例子:

>>> print('hello, world')  # 注意到 print 是一个函数
>>>

在上面的注释中,我们提到了函数,函数(function)是指可重复使用的程序片段,在下一章我们会有详细说明。

空值(None)

Python 中使用 None 来表示空值,它是一个特殊的对象。在交互环境中输入 None 并不会返回任何东西:

>>> None
>>>

数字(numeric)

Python 中的数字有整数(int)和浮点数(float)两种,浮点数就是我们日常说的小数,输入一个数字会原样打印它:

>>> 3
3
>>> 2017
2017
>>> 3.14
3.14
>>> -5
-5
>>>

很自然地,你在数学上能用的运算符也能用到它身上:

>>> 1 + 2
3
>>> 1.0 + 2
3.0
>>> 5 - 3
2
>>> 3 * 4
12
>>> 4 / 2
2.0
>>> 4 // 2
2
>>> 9 % 2
1
>>> 2 ** 4
16
>>>

上面是常见的运算符,总结如下表:

运算符 描述 运算符 描述
+ 加法 // 整数除法
- 减法 % 模(求余)
* 乘法 **
/ 浮点数除法    

浮点数和整数参与运算时,结果为浮点数,浮点数除法的结果也为浮点数。

字符串(str)

字符串是不可变的序列类型(sequence type),它是字符的序列。将一系列字符包裹在一对单引号或一对双引号中即可创建字符串,就像下面这样:

>>> 'hello, fanfou'
'hello, fanfou'
>>> "hello, fanfou"
'hello, fanfou'
>>> 'hello, 2017'
'hello, 2017'
>>>

Python 提供了两种方式来表示字符串,这为我们提供了一些便利,当你想在字符串中包含一个单引号,你可以用双引号括住字符串,反之亦然:

>>> "what's this"
"what's this"
>>> 'this is an "apple"'
'this is an "apple"'
>>>

事实上,Python 还提供了第三种表示字符串的方式,用一对连续的三个单引号或双引号扩住,引号和换行都可以用在这种字符串中:

>>> '''what's this'''
"what's this"
>>> """this is an "apple\""""
'this is an "apple"'
>>> '''hello,
... fanfou'''
'hello,\nfanfou'
>>>

需注意的是,在三引号这种写法中,如果句中引号在开始或结束的附近,还是需要转义的,就像上面的 "apple\",因为如果不这样,连续的三个双引号就会被当成结束,而多出了一个造成语法错误。

在上面中最后一个语句中,… 并不需要输入,这是 Python 在等待你继续输入的提示符,和 >>> 一样是提示符。

你会看到换行在输出的时候变成了 \n ,这是因为在 Python 的字符串中 \n 表示换行,这种格式的字符叫做转义字符, 使用一个反斜杠(\)开头,紧跟着一个字符,如 \n 表示换行,转义字符用来表示一些常见的不能在屏幕打印的字符。

想在单引号字符中包含单引号需使用 ',同理在双引号字符串中需使用 ",如果你需要表示反斜杠本身,那么你需要使用两个连续的 \:

>>> 'what\'s this'
"what's this"
>>> "this is an \"apple\""
'this is an "apple"'
>>> print('hello, \nfanfou')
hello,
fanfou
>>> print('hello, \\nfanfou')
hello, \nfanfou
>>>

在 Python 的交互环境中,输入字符串会原样返回,为了看出 n 的效果,我们使用了内置函数 print(),它的作用是向屏幕打印。

Python 中还有一种字符串叫做原生字符串,以字母 r 开头,在原生字符串中转义字符不会被转义,即会原样输出:

>>> print(r'hello, \nfanfou')
hello, \nfanfou
>>>

使用内置函数 str() 可以把数字转换成字符串:

>>> 16
16
>>> str(16)
'16'
>>> 3.1415
3.1415
>>> str(3.1415)
'3.1415'
>>>

变量(variable)

在上面的代码中,我们重复输入了两次 16 和 3.1415,有没有办法可以减少这种重复劳动呢,答案是使用变量。所以在继续学习其他数据类型之前,我们先来学习一下变量, 就像我们都有名字一样,而名字可以用来指代一个人,在 Python 中也可以用一个名字来指代一个对象。

这种名字就叫做变量,使用赋值操作符(=)可以定义一个变量,就像下面这样:

>>> a = 2
>>> b = 3
>>> a + b
5
>>> c = a * b
>>> c
6
>>> a = 10
>>> a
10
>>> A = 12
>>> A
12
>>> a
10
>>>

变量的名字可以使用字母或下划线(_)开头,而后可跟任意个字母、数字或下划线。变量可以赋值给其他变量,也可以重新赋值,还有它们对字母大小写敏感,所以在上面 a 和 A 的值不同。

最后要注意的是,不要使用下面这些词作为变量名,它们是 Python 保留的关键字:

False class finally is return
None continue for lambda try
True def from nonlocal while
and del global not with
as elif if or yield
assert else import pass  
break except in raise  

使用内置函数 type() 可以确定一个对象的类型,有了变量我们再来做一次类型转换:

>>> a = 4
>>> b = '8'
>>> c = '3.1415'
>>> type(a)
<class 'int'>
>>> a = str(a)
>>> type(a)
<class 'str'>
>>> type(b)
<class 'str'>
>>> b = int(b)
>>> type(b)
<class 'int'>
>>> type(c)
<class 'str'>
>>> c = float(c)
>>> type(c)
<class 'float'>
>>>

使用内置函数 int() 或 float() 可以把看着像数字的字符串转换成整形或浮点型。

字节(bytes)

字节对象是单个字节的不可变序列,每个字节可以是 ascii 字符或者是从 x00 到 xff 的 16 进制数。 字节对象的创建和字符串的创建很相像,但它需要在引号前加上字符 b,就像下面这样:

>>> b'hello'
b'hello'
>>>

字节对象和字符串可以相互转换:

>>> s = b'hello'
>>> s.decode()
'hello'
>>> s = 'fanfou'
>>> s.encode()
b'fanfou'
>>>

基于上面的描述和示例代码,你可能会有疑问,为什么 Python 中要有两种这么相像的类型,而且还可以相互转换。 要解释清楚这个,我们先要了解一点点字符编码的概念,这里有个我认为比较易懂的文章: 字符编码笔记:ASCII,Unicode 和 UTF-8 - 阮一峰的网络日志

字符串和字节对象的区别在于,字符串是抽象的而字节对象是具体。在 Python 3 中,所有字符串都是 Unicode 字符的序列,这并没有指明它在计算机内部的表示方式,它是抽象的表示。 而字节对象会表明每一个字节在计算机的内部表示。

在上面的示例代码,我们看不出什么区别,我们用中文字符串来看一下:

>>> s = '饭否'
>>> s
'饭否'
>>> s.encode('utf8')
b'\xe9\xa5\xad\xe5\x90\xa6'
>>> s.encode('gbk')
b'\xb7\xb9\xb7\xf1'
>>>

你会看到,我们把字符串编码成字节对象后,它变成了一系列的 16 进制数的表示,我们还可以指定不同的编码方式(假设你已经浏览了阮一峰老师的那篇笔记),得到的结果也不一样。

那么什么时候该用字符串而什么时候要用字节对象呢,当需要保存到磁盘上或在网络传输的时候,需要把字符串编码(encode())成字节对象,而其他时候我们使用字符串,因为它更直观。

如果上面的解释你看得有些糊涂,没有关系的,我们在编程中更多地和字符串打交道。你只需知道二者可相互转换,并且记住二者的使用场景即可。另外这都是我的错,是我没能解说清楚。

列表(list)

列表是可变的序列,可以包含零或多个元素,元素可以是 Python 中任何类型的对象。 用逗号来分隔一系列元素,并把它们包裹在一对中括号中即可创建列表,就像下面这样:

>>> [1, 2, 3, 3]
[1, 2, 3, 3]
>>> [1, 'a', 'b']
[1, 'a', 'b']
>>> [1, [2, 3]]
[1, [2, 3]]
>>>

序列的一个很重要的操作是索引取值:

>>> foo = [1, 2, 3, 4, 5, 6]
>>> foo
[1, 2, 3, 4, 5, 6]
>>> foo[0]
1
>>> foo[5]
6
>>>

在上面我们把一个列表赋值给了一个变量 foo,输入 foo 可以验证 Python 确实这样做了。输入 foo[0] 是为了取得 foo 的第一个值,在计算机中下标一般是从 0 开始的, 所以 foo[5] 表示的是取得 foo 最后一个值。你可以尝试输入 foo[6],你会得到一个出错信息(提示越界),不用担心,这不会损害你的计算机。

字符串同样可以索引取值,我们来试一下:

>>> bar = 'fanfou'
>>> bar
'fanfou'
>>> bar[0]
'f'
>>> bar[2]
'n'
>>>

元组(tuple)

元组和列表很相似,但它是不可变的序列结构。用逗号来分隔一系列元素,并把它们包裹在一对圆括号中即可创建元组,就像下面这样:

>>> (1, 2, 3, 3)
(1, 2, 3, 3)
>>> baz = (1, 'a', 'c', (3, 4))
>>> baz[0]
1
>>> baz[2]
'c'
>>> baz[3]
(3, 4)
>>> baz[3][1]
4
>>> foo = (1,)
>>> foo
(1,)
>>> foo = (1)
>>> foo
1
>>>

可以看到,元组和列表一样使用索引取值,而且你还看到了嵌套的索引取值,这在列表中也是可行的。 需要注意的是,如果你的元组只有一个元素,需要在元素后面加上逗号,否则你得不到你想要的(元组)。

我们来看看列表(可变序列)和元组(不可变序列)的区别,列表被创建之后可以修改,而元组不被允许修改:

>>> item = [1, 2, 3]
>>> item
[1, 2, 3]
>>> item.append(4)
>>> item
[1, 2, 3, 4]
>>> item[0] = 5
>>> item
[5, 2, 3, 4]
>>> del item[1]
>>> item
[5, 3, 4]
>>>

我们说过在 Python 中全部的东西都是对象,对象可拥有属于自己的函数,我们称之为方法。对象可通过操作符号属性引用(.)来使用方法,就像上面 item.append() 那样。 在列表中,可使用 append() 方法来增加一个元素,而通过下标索引并赋给一个新值可修改元素,使用关键字 del 和下标即可删除元素。

如果你尝试在元组中做上面这些操作,你会得到一个错误提示,我们来试一下:

>>> item = (1, 2, 3)
>>> item
(1, 2, 3)
>>> item[0] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> del item[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion
>>>

因为元组没有 append() 的方法,所以我们只试了修改和删除元素,都得到了错误信息。

在介绍字符串的时候,我们也说了字符串是不可变的字符序列,我们来修改一下试试:

>>> name = 'Mr.greeting'
>>> name[0] = 'm'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>

尝试修改 name 的时候,我们也得到了错误提示。稍后我会介绍当我们想修改字符串的时候该怎么办。

使用内置函数 len() 可以得到序列对象的长度:

>>> s = 'hello, my little bot'
>>> len(s)
20
>>> a = [1, 2, 3, 4, 5, 6]
>>> len(a)
6
>>> b = ('a', 'b', 'c', 'd')
>>> len(b)
4
>>>

在 Python 中,序列对象的切片是很常用的操作,可以取得子序列:

>>> s = 'hello, fanfou'
>>> s[0:5]
'hello'
>>> s[:5]
'hello'
>>> s[7:]
'fanfou'
>>> s[-6:]
'fanfou'
>>> t = [1, 2, 3, 4, 5]
>>> t[0:2]
[1, 2]
>>> t[:2]
[1, 2]
>>> t[::2]
[1, 3, 5]
>>> t[::-1]
[5, 4, 3, 2, 1]
>>>

在方括号中([]),你可以指定三个值,每个值用冒号(:)隔开,每个值都是可选的,但当你想只提供后面位置的值时,前面要加上冒号(:),如 [:x] 和 [::x](x 指某一整数)。 第一个是起点,默认是 0,负值时从末尾数起;第二个是终点,默认为 len() - 1,负值时从末尾数起;第三个值是步进,即每隔多少个位置取一个元素,默认为 1,负值时从末尾数起。

我们来看看当想修改字符串时该怎样做:

>>> s = 'fanfou'
>>> s = 'F' + s[1:]
>>> s
'Fanfou'
>>>

上面的代码仅作为示例子,它让我们看到当想修改字符串的时候可以截取原字符串的子序列与新的字符串想加,再赋值给原变量。事实上,当我们想修改英文字符串的大小写有更方便的方法:

>>> s1 = 'hello'
>>> s2 = 'FANFOU'
>>> s1.upper()
'HELLO'
>>> s2.lower()
'fanfou'
>>> 'hello, fanfou'.title()
'Hello, Fanfou'
>>>

我们知道字符串和列表都是序列类型,它们可以很方便地转换,我们来看看:

>>> s = 'hello'
>>> list(s)
['h', 'e', 'l', 'l', 'o']
>>> arr = ['f', 'a', 'n', 'f', 'o', 'u']
>>> ''.join(arr)
'fanfou'
>>> '-'.join(arr)
'f-a-n-f-o-u'
>>>

使用内置函数 list() 可以把字符串转换成列表,而函数 join() 能使用一个字符串把列表拼接起来。

列表和元组同样能相互转换:

>>> a = [1, 2, 3]
>>> b = (4, 5, 6)
>>> tuple(a)
(1, 2, 3)
>>> list(b)
[4, 5, 6]
>>>

所以当我们想修改一个元组的时候可以先把它转换成列表,修改后再转换回元组。

下面我们来看看列表和元组的排序:

>>> a = [5, 2, 3, 9, 7]
>>> b = (5, 2, 3, 9, 7)
>>> sorted(a)
[2, 3, 5, 7, 9]
>>> sorted(b)
[2, 3, 5, 7, 9]
>>> a
[5, 2, 3, 9, 7]
>>> b
(5, 2, 3, 9, 7)
>>>

使用内置函数 sorted() 对列表或元组排序,都会返回一个排序后的列表,而原对象并不会修改。

因为列表是可变的序列,它还支持原处排序(修改原对象),使用方法 sort() 即可:

>>> a = [5, 2, 3, 9, 7]
>>> a.sort()
>>> a
[2, 3, 5, 7, 9]
>>>

元组并不支持在对象上使用 sort() 方法,因为它是不可变的。所以为了一致性的调用方式,我们更常使用内置函数 sorted() 来排序。

如果想倒序排序,只需再向 sorted() 传递一个参数 reverse=True 即可:

>>> a = [5, 2, 3, 9, 7]
>>> sorted(a, reverse=True)
[9, 7, 5, 3, 2]
>>>

关于函数调用以及传递参数,我们在下一章会详细的说明,现在只需对这些内置的函数的使用方法有些印象即可。

字典(dict)

字典是一种键值对的映射类型(mapping type),与序列类型不同,它是无序的。每个元素拥有与之对应的互不相同的键(key),需要通过键来访问元素。 键通常是字符串,但它还可以是 Python 中其他任意的不可变类型,值可以是任意类型的对象。 用冒号(:)分隔键值,每个键值对用逗号(,)分隔,并把它们包裹在大括号对中即可创建字典,就像下面这样:

>>> d = {'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> d
{'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> d['id']
'TestByTse'
>>> d['name']
'Mr.Greeting'
>>>

当你尝试访问一个不存在的键时,你会得到一个出错信息:

>>> d['gender']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'gender'
>>> d.get('gender')
>>> d.get('gender', 'man')
'man'
>>>

这时我们可以使用字典的 get() 方法来避免出错,当键不存在时会返回 None,还可以给 get() 方法提供默认值,当键不存在时返回默认值。

字典对象是可变的,它被创建后可以增加、修改或删除元素:

>>> d
{'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> d['age'] = 5
>>> d
{'age': 5, 'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> d['name] = 'Mr.G'
>>> d
{'age': 5, 'id': 'TestByTse', 'name': 'Mr.G'}
>>> del d['age]
>>> d
{'id': 'TestByTse', 'name': 'Mr.G'}
>>>

如果键不存在的时候,赋值将会增加元素,如果存在了将会修改它的值。请注意到 age 是我们后来增加的,但查看字典 d 的时候,它比其他的键值对先显示。字典的键值对顺序和我们增改的顺序并不对应,请记住字典是无序的。

内置函数 len() 同样能用在字典上,它会返回字典的长度:

>>> d = {'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> len(d)
2
>>>

字典有几个常用的方法,我们来看一下:

>>> d = {'id': 'TestByTse', 'name': 'Mr.Greeting'}
>>> d.keys()
dict_keys(['id', 'name'])
>>> d.values()
dict_values(['TestByTse', 'Mr.Greeting'])
>>> d.items()
dict_items([('id', 'TestByTse'), ('name', 'Mr.Greeting')])
>>>

方法 keys() 和 values() 会分别返回键和值的列表(可以近似把它看作列表),而 items() 会返回一个元素为键和值组成的元组的列表。

集合(set)

集合的意义和数学上的意义一致,它是一种无序、元素唯一的对象。用逗号来分隔一系列元素,并把它们包裹在大括号对中即可创建集合,就像下面这样:

>>> a = {1, 2, 3, 4, 4}
>>> a
{1, 2, 3, 4}
>>> b = {'a', 'b', 'c', 3, 4}
>>> b
{'a', 'b', 'c', 3, 4}
>>> a & b
{3, 4}
>>> a.union(b)
{1, 2, 3, 4, 'a', 'b', 'c'}
>>> b.union(a)
{1, 2, 3, 4, 'a', 'b', 'c'}
>>>

在 a 中,我们输了两个 4,查看 a 发现重复的元素去掉了,这符合我们的预想。两个集合可以做交集或并集,这和数学上的操作是一样的。

因为集合和字典都使用花括号定义,当我们要一个空集合的时,写法要有些不一样,以用来和字典区分开来:

>>> {}  # 空字典
{}
>>> set()  # 空集合
set()
>>>

布尔(bool)

布尔是表示真假的类型,仅包含 True 和 False 两种取值。Python 中所有的对象都可以被测试得到真或假值,下面列出的对象被当做假(False):

None 空字符串 空元组
False 空字节 空字典
数字零 空列表 空集合

其他所以的对象都被当做真。内置函数 bool() 可以很方便得到对象的布尔值:

>>> bool(0)
False
>>> bool(0.0)
False
>>> bool('')
False
>>> bool('False')
True
>>>

有三个运算符 and、or 和 not 可用来进行布尔(逻辑)运算,下表是官方文档给出的说明:

运算 结果
x or y 如果 x 为假,结果为 y,否则为 x
x and y 如果 x 和 y 均为假,结果为 x,否则为 y
not x 如果 x 为假,结果为 True,否则为 False

上表中的真或假指对象被测试后得到的真或假值,而不仅仅指 True 或 False。我们来具体看一下:

>>> x = 1
>>> y = 0
>>> z = False
>>> x and y
0
>>> x or y
1
>>> x and z
False
>>> not x
False
>>> not y
True
>>> not z
True
>>>

虽然对象进行布尔运算后返回的不一定是直接的 True 或 False,但我们上面说过,Python 中全部的对象都可以被测试得到真或假值。 最后提一下,and 和 or 是短路运算符(short-circuit operator),这意味着仅当求值左边的对象不能得到结果时才求值右边的对象。

认识布尔类型后,我们来看一下比较运算符,运算的结果是布尔值,下表是这些运算符的说明:

运算符 含义
< 小于
<= 小于或等于
> 大于
>= 大于或等于
== 相等
!= 不相等
is 同一对象
is not 不同对象

我们来实际看一下:

>>> 5 > 3
True
>>> -1 < 0
True
>>> 2 <= 2
True
>>> 2 >= 2
True
>>> 2 < 3 < 4
True
>>> 10 / 5 == 5 - 3
True
>>> 'hello' == "hello"
True
>>> 'Hello' == 'hello'
False
>>>

在计算机编程中,我们一般使用等号(=)来赋值,而使用双等号(==)来进行相等比较。在 10 / 5 == 5 - 3 这句中,你可能会觉不自然, Python 先计算出双等号(==)两边的值后,再进行比较。运算符的优先等级我们稍后会进行介绍。大小写不同的字符串是不同的两个字符串。

对于序列类型、字典和集合等,有个很重要的成员测试运算符 in,它返回一个布尔值:

>>> s = 'hello, fanfou'
>>> a = [1, 3, 5, 7, 9]
>>> b = (0, 2, 4, 6, 8)
>>> A = {1, 2, 3}
>>> 'hello' in s
True
>>> 'Hello' in s
False
>>> 1 in a
True
>>> 3 in b
False
>>> 2 in A
True
>>>

刚才我有提到字典,但在上面的示例代码中却没有,我是故意遗漏的,把 in 用在字典上不是一个好的风格,我们更鼓励使用字典的 get() 方法。

优先级(priority)

下面我们来看一下运算符优先级和结合性(associativity),来结束本章(预告一下,还有两章我们就可以进入正式开发了,请暂且对我保持耐性)。

如果你有一个诸如 2 + 3 * 4 的表达式,你应该知道要算乘法再算加法,这意味着乘法运算符的优先级要高于加法运算符。但你也可以用圆括号来改变运算顺序,比如 (2 + 3) * 4。 运算符具有不同的优先级,如果没有使用圆括号去改变它们的运算顺序,那么它们将从优先级高往低进行运算。

为了保持完整性,下表从 官方文档 复制而来,对于你还没见过的运算符可以先忽略:

运算符 描述
lambda Lambda 表达式
if - else 条件表达式
or 布尔或
and 布尔与
not x 布尔非
in, not in, is, is not, <, <=, >, >=, !=, == 比较,包括成员资格测试和身份测试
| 按位或
^ 按位异或
& 按位与
<<, >> 按位移
+, - 加与减
*, /, //, % 乘、除、整除、取余
+x, -x, ~x 正、负、按位取反
** 求幂
x[index], x[index:index], x(arguments…), x.attribute 下标、切片、调用、属性引用
(expressions…), [expressions…], {key: value…}, {expressions…} 绑定或元组显示、列表显示、字典显示、集合显示

在上表中,下行的优先级比上行的高,同一行的优先级相同,例如 + 和 - 优先级相同。

运算符通常由左至右结合,这意味着具有相同优先级的运算符将从左至右的方式依次进行求值。如 2 + 3 + 4 将会以 (2 + 3) + 4 的形式加以计算。

对于赋值表达式,则是先求值等号(=)右边再赋值给变量,如 a = 2 + 3 * 4,a 的值将为 14。另外对于数值运算与赋值,Python 还有简便的表示:

>>> a = 3
>>> a = a + 1
>>> a
4
>>> a += 1
>>> a
5
>>>

本章到此结束了,请休整一下进入下一章,感谢你的阅读。