注释

加入注释并不会对代码的运行产生影响,但加入注释可以使代码更加易懂易用。

1
2
3
4
5
6
7
# 用 # 字符开头的是单行注释

"""
跨多行字符串会用三引号
(即三个单引号或三个双引号)
包裹,但也通常被用于注释
"""

一切皆对象

在 Python 中,你无需事先声明变量名及其类型,直接赋值即可创建各种类型的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> x = -3  # 语句结尾不用加分号
>>> x
-3
>>> f = 3.1415926535897932384626; f # 实在想加分号也可以,这里节省了一行
3.141592653589793
>>> s1 = "O"
>>> s1 # 在 Python 中双引号和单引号的作用相同
'O'
>>> b = 'A' == 65 # 'A' 和 65 不是一个数据类型,所以不相等
>>> b # True, False 首字母均大写
False
>>> True + 1 == 2 and not False != 0 # Python 中的表达式中大多使用单词,但是也支持符号
True

这不代表 Python 没有类型的概念,实际上解释器会根据赋值或运算自动推断变量类型,你可以使用内置函数 type() 查看这些变量的类型:

1
2
3
4
5
6
7
8
>>> type(x)
<class 'int'>
>>> type(f)
<class 'float'>
>>> type(s1) # 不要给字符串起名为 str,否则 str 对象会被篡改
<class 'str'>
>>> type(b)
<class 'bool'>

数字运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> 5.0 * 6  # 浮点数的运算结果是浮点数
30.0
>>> 15 / 3 # 与 C/C++ 不同,除法永远返回浮点 float 类型
5.0
>>> 5 / 100000 # 位数太多,结果显示成科学计数法形式
5e-05
>>> 5 // 3 # 使用整数除法(地板除)则会向下取整,输出整数类型
1
>>> -5 // 3 # 符合向下取整原则,注意这与 C/C++ 不同
-2
>>> 5 % 3 # 取模
2
>>> -5 % 3 # 负数取模结果一定是非负数,这点也与 C/C++ 不同,不过都满足 (a//b)*b+(a%b)==a
1
>>> x = abs(-1e4) # 求绝对值的内置函数
>>> x += 1 # 没有自增/自减运算符
>>> x # 科学计数法默认为 float
10001.0

除法运算(/)永远返回浮点类型(在 Python 2 中返回整数)。如果想要整数或向下取整的结果的话,可以使用整数除法(//)。同样的,也可以像 C++ 中一样,使用模(%)来计算余数,科学计数法的形式也相同。

特别地,Python 用 ** 即可进行幂运算,还通过内置的 pow(a, b, mod) 提供了 快速幂 的高效实现。

1
2
3
4
5
6
7
8
>>> 3 ** 4 # 幂运算
81
>>> 2 ** 512
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
>>> pow(2, 512, int(1e4)) # 即 2**512 % 10000 的快速实现, 1e4 是 float 所以要转 int
4096
>>> 0.1 + 0.1 + 0.1 - 0.3 == 0. # 和 C/C++ 一样需要注意浮点数不能直接判相等
False

数据类型判断

对于一个变量,可以使用type(object)返回变量的类型,例如type(8)type('a')的值分别为 <class 'int'><class 'str'>

Python 中的输入输出主要通过内置函数 input() 和 print() 完成,print() 的用法十分符合直觉:

1
2
3
4
5
6
7
8
9
>>> a = [1,2,3]; print(a[-1])  # 打印时默认末尾换行
3
>>> print(ans[0], ans[1]) # 可以输出任意多个变量,默认以空格间隔
1 2
>>> print(a[0], a[1], end='') # 令 end='', 使末尾不换行
1 2>>>
>>> print(a[0], a[1], sep=', ') # 令 sep=', ',改变间隔样式
1, 2
>>> print(str(a[0]) + ', ' + str(a[1])) # 输出同上,但是手动拼接成一整个字符串

input() 函数的行为接近 C++ 中的 getline(),即将一整行作为字符串读入,且末尾没有换行符。

1
2
3
>>> s = input('请输入一串数字: '); s  # 调试时可以向 input() 传入字符串作为提示
请输入一串数字: 1 2 3 4 5 6
'1 2 3 4 5 6'

字符串

Python 3 提供了强大的基于 Unicode 的字符串类型,使用起来和 C++ 中的 string 类似,一些概念如转义字符也都相通,除了加号拼接和索引访问,还额外支持数乘 * 重复字符串,和 in 操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> s1 = "O"  # 单引号和双引号都能包起字符串,有时可节省转义字符
>>> s1 += 'I-Wiki' # 为和 C++ 同步建议使用双引号
>>> 'OI' in s1 # 检测子串很方便
True
>>> len(s1) # 类似 C++ 的 s.length(),但更通用
7
>>> s2 = """ 感谢你的阅读
... 欢迎参与贡献!
""" # 使用三重引号的字符串可以跨越多行
>>> s1 + s2
'OI-Wiki 感谢你的阅读\n欢迎参与贡献!'
>>> print(s1 + s2) # 这里使用了 print() 函数打印字符串
OI-Wiki 感谢你的阅读
欢迎参与贡献!
>>> s2[2] * 2 + s2[3] + s2[-1] # 负数索引从右开始计数,加上 len(s),相当于模 n 的剩余类环
'谢谢你!'
>>> s1[0] = 'o' # str 是不可变类型,不能原地修改,其实 += 也是创建了新的对象
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

Python 支持多种复合数据类型,可将不同值组合在一起。最常用的 list,类型是用方括号标注、逗号分隔的一组值。例如,[1, 2, 3] 和 [‘a’,’b’,’c’] 都是列表。

除了索引,字符串还支持切片,它的设计非常精妙,格式为 s[左闭索引:右开索引:步长]:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> s = 'OI-Wiki 感谢你的阅读\n欢迎参与贡献!'
>>> s[:8] # 省略左闭索引则从头开始
'OI-Wiki '
>>> s[8:14] # 左闭右开设计的妙处,长度为 14-8=6,还和上一个字符串无缝衔接
'感谢你的阅读'
>>> s[-4:] # 省略右开索引则直到结尾
'与贡献!'
>>> s[8:14:2] # 步长为2
'感你阅'
>>> s[::-1] # 步长为 -1 时,获得了反转的字符串
'!献贡与参迎欢\n读阅的你谢感 ikiW-IO'
>>> s # 但原来的字符串并未改变
'OI-Wiki 感谢你的阅读\n欢迎参与贡献!'

在最新的 Python 3 版本中,字符串是以 Unicode 编码的,也就是说,Python 的字符串支持多语言。1在 Python 中,可以对一个 Unicode 字符使用内置函数 ord() 将其转换为对应的 Unicode 编码,逆向的转换使用内置函数 chr()。C/C++ 中 char 类型也可以和 对应的 ASCII 码互转。

如果想把数字转换成对应的字符串,可以使用内置函数 str(),反之可以使用 int() 和 float(),你可以类比为 C/C++ 中的强制类型转换,但括号不是加在类型上而是作为函数的一部分括住参数。

Python 的字符串类型提供了许多强大的方法,包括计算某字符的索引与出现次数,转换大小写等等。

创建数组

列表(list)大概是 Python 中最常用也最强大的序列类型,列表中可以存放任意类型的元素,包括嵌套的列表,这符合数据结构中「广义表」的定义。请注意不要将其与 C++ STL 中的双向链表 list 混淆,故本文将使用「列表」而非 list 以免造成误解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> []  # 创建空列表,注意列表使用方括号
[]
>>> nums = [0, 1, 2, 3, 5, 8, 13]; nums # 初始化列表,注意整个列表可以直接打印
[0, 1, 2, 3, 5, 8, 13]
>>> nums[0] = 1; nums # 支持索引访问,支持修改元素
[1, 1, 2, 3, 5, 8, 13]
>>> nums.append(nums[-2]+nums[-1]); nums # append() 同 vector 的 push_back(),也都没有返回值
[1, 1, 2, 3, 5, 8, 13, 21]
>>> nums.pop() # 弹出并返回末尾元素,可以当栈使用;其实还可指定位置,默认是末尾
21
>>> nums.insert(0, 1); nums # 同 vector 的 insert(position, val)
[1, 1, 1, 2, 3, 5, 8, 13]
>>> nums.remove(1); nums # 按值移除元素(只删第一个出现的),若不存在则抛出错误
[1, 1, 2, 3, 5, 8, 13]
>>> len(nums) # 求列表长度,类似 vector 的 size(),但 len() 是内置函数
7
>>> nums.reverse(); nums # 原地逆置
[13, 8, 5, 3, 2, 1, 1]
>>> sorted(nums) # 获得排序后的列表
[1, 1, 2, 3, 5, 8, 13]
>>> nums # 但原来的列表并未排序
[13, 8, 5, 3, 2, 1, 1]
>>> nums.sort(); nums # 原地排序,可以指定参数 key 作为排序标准
[1, 1, 2, 3, 5, 8, 13]
>>> nums.count(1) # 类似 std::count()
2
>>> nums.index(1) # 返回值首次出现项的索引号,若不存在则抛出错误
0
>>> nums.clear(); nums # 同 vector 的 clear()

以上示例展现了列表与 vector 的相似之处,vector 中常用的操作一般也都能在列表中找到对应方法,不过某些方法如 len(),sorted() 会以内置函数的面目出现,而 STL 算法中的函数如 find(),count(),max_element(),sort(),reverse() 在 Python 中又成了对象的方法,使用时需要注意区分。

将展示列表作为 Python 的基本序列类型的一些强大功能:

Python 支持多种复合数据类型,可将不同值组合在一起。最常用的 list,类型是用方括号标注、逗号分隔的一组值。例如,[1, 2, 3] 和 [‘a’,’b’,’c’] 都是列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> lst = [1, '1'] + ["2", 3.0]  # 列表直接相加生成一个新列表
>>> lst # 这里存放不同的类型只是想说明可以这么做,但这不是好的做法
[1, '1', '2', 3.0]
>>> 3 in lst # 实用的成员检测操作,字符串也有该操作且还支持子串检测
True
>>> [1, '1'] in lst # 仅支持单个成员检测,不会发现「子序列」
False
>>> lst[1:3] = [2, 3]; lst # 切片并赋值,原列表被修改
[1, 2, 3, 3.0]
>>> lst[::-1] # 获得反转后的新列表
[3.0, 3, 2, 1]
>>> lst *= 2; lst # 数乘拼接
[1, 2, 3, 3.0, 1, 2, 3, 3.0]
>>> del lst[4:]; lst # 也可写 lst[4:] = [],del 语句不止可以用于删除序列中元素
[1, 2, 3, 3.0]

列表作为序列的一些常用操作,可以看出许多操作如切片是与字符串相通的,但字符串是「不可变序列」而列表是「可变序列」,故可以通过切片灵活地修改列表。在 C/C++ 中我们往往会通过循环处理字符数组,下面将展示如何使用 「列表推导式」 在字符串和列表之间转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
>>> # 建立一个 [65, 70) 区间上的整数数组,range 也是一种类型,可看作左闭右开区间,第三个参数为步长可省略
>>> nums = list(range(65,70)) # 记得 range 外面还要套一层 list()
[65, 66, 67, 68, 69]
>>> lst = [chr(x) for x in nums] # 列表推导式的典型结构,[exp for var in iterable if cond]
>>> lst # 上两句可以合并成 [chr(x) for x in range(65,70)]
['A', 'B', 'C', 'D', 'E']
>>> s = ''.join(lst); s # 用空字符串 '' 拼接列表中的元素生成新字符串
'ABCDE'
>> list(s) # 字符串生成字符列表
['A', 'B', 'C', 'D', 'E']
>>> # 如果你不知道有 s.lower() 方法就可能写出下面这样新瓶装旧酒的表达式
>>> ''.join([chr(ord(ch) - 65 + 97) for ch in s if ch >= 'A' and ch <= 'Z'])
'abcde'

>>> vis = [[0] * 3] * 3 # 开一个 3*3 的全 0 数组
>>> vis
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> vis[0][0] = 1; vis # 怎么会把其他行也修改了?
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
>>> # 先来看下一维列表的赋值
>>> a1 = [0, 0, 0]; a2 = a1; a3 = a1[:] # 列表也可以直接被赋给新的变量
>>> a1[0] = 1; a1 # 修改列表 a1,似乎正常
[1, 0, 0]
>>> a2 # 怎么 a2 也被改变了
[1, 0, 0]
>>> a3 # a3 没有变化
[0, 0, 0]
>>> id(a1) == id(a2) and id(a1) != id(a3) # 内置函数 id() 给出对象的「标识值」,可类比为地址,地址相同说明是一个对象
True
>>> vis2 = vis[:] # 拷贝一份二维列表
>>> vis[0][1] = 2; vis # vis 会被批量修改
>>> [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
>>> vis2 # 但 vis2 是切片拷贝还是被改了
>>> [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
>>> id(vis) != id(vis2) # vis 和 vis2 不是一个对象
True
>>> # vis2 虽然不是 vis 的引用,但其中对应行都指向相同的对象
>>> [id(vis[i]) == id(vis2[i]) for i in range(3)]
[True, True, True]
>>> # 回看二维列表自身
>>> [id(x) for x in vis] # 具体数字和这里不一样但三个值一定相同,说明是三个相同对象
[139760373248192, 139760373248192, 139760373248192]

Python 中赋值只传递了引用而非创建新值,可以创建不同类型的变量并赋给新变量,验证发现二者的标识值是相同的,只不过直到现在我们才介绍了列表这一种可变类型,而给数字、字符串这样的不可变类型赋新值时实际上创建了新的对象,故而前后两个变量互不干扰。但列表是可变类型,所以我们修改一个列表的元素时,另一个列表由于指向同一个对象所以也被修改了。创建二维数组也是类似的情况,示例中用乘法创建二维列表相当于把 [0]*3 这个一维列表重复了 3 遍,所以涉及其中一个列表的操作会同时影响其他两个列表。更不幸的是,在将二维列表赋给其他变量的时候,就算用切片来拷贝,也只是「浅拷贝」,其中的元素仍然指向相同的对象,解决这个问题需要使用标准库中的 deepcopy,或者尽量避免整个赋值二维列表。不过还好,创建二维列表时避免创建重复的列表还是比较简单,只需使用「列表推导式」:

1
2
3
4
5
6
7
8
9
>>> vis1 = [[0] * 3 for _ in range(3)]  # 把用不到的循环计数变量设为下划线 _ 是一种惯例
>>> # 但在 REPL 中 _ 默认指代上一个表达式输出的结果,故也可使用双下划线
>>> vis1
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> [id(x) for x in vis1] # 具体数字和这里不一样但三个值一定不同,说明是三个不同对象
[139685508981248, 139685508981568, 139685508981184]
>>> vis1[0][0] = 1
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> a2[0][0] = 10 # 访问和赋值二维数组

Python 是高度动态的解释型语言,因此其程序运行有大量的额外开销。尤其是 for 循环在 Python 中运行的奇慢无比。因此在使用 Python 时若想获得高性能,尽量使用列表推导式,或者 filter,map 等内置函数直接操作整个序列来避免循环,当然这还是要根据具体问题而定。

NumPy

利用 NumPy 建立多维数组并进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> import numpy as np  
>>> np.empty(3) # 开容量为 3 的空数组,注意没有初始化为 0
array([0.00000000e+000, 0.00000000e+000, 2.01191014e+180])
>>> np.zeros((3, 3)) # 开 3*3 的数组,并初始化为 0
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
>>> a1 = np.zeros((3, 3), dtype=int) # 开 3×3 的整数数组
>>> a1[0][0] = 1 # 访问和赋值
>>> a1[0, 0] = 1 # 更友好的语法
>>> a1.shape # 数组的形状
(3, 3)

>>> a1[:2, :2] # 取前两行、前两列构成的子阵,无拷贝
array([[1, 0],
[0, 0]])

>>> a1[:, [0, 2]] # 获取第 1、3 列,无拷贝
array([[1, 0],
[0, 0],
[0, 0]])
>>> np.max(a1) # 获取数组最大值
1
>>> a1.flatten() # 将数组展平
array([1, 0, 0, 0, 0, 0, 0, 0, 0])

>>> np.sort(a1, axis = 1) # 沿行方向对数组进行排序,返回排序结果
array([[0, 0, 1],
[0, 0, 0],
[0, 0, 0]])
>>> a1.sort(axis = 1) # 沿行方向对数组进行原地排序

array

array 是 Python 标准库提供的一种高效数值数组,可以紧凑地表示基本类型值的数组,但不支持数组嵌套