Python 快速入门
2020-01-24 Python Python Quick Start
- 前言
- 安装
- Python 文件执行方法
- Hello World
- 关键字
- 基本语法格式
- 变量
- 数据类型
- 条件表达式
- 循环表达式
- 列表
- 元组
- 字典
- 集合
- 函数
- 类和面向对象
- 异常
- 模块
- 单元测试
- API 速查表
- 扩展阅读
前言
人生苦短,我用 Python。
Python 语言的最大特点就是足够简洁,有任意编程语言的开发者,都可以看着手册直接上手快速开发,尤其是对于已经有 Golang 开发经验的开发者,感觉会更加熟悉。
本文所有代码的 Python 运行环境:
$ python --version
Python 3.12.3
‼️注意: 如果读者是较低版本,部分代码输出示例可能和文中不同。
本文内容参照 Python 从入门到实践 目录顺序,时间充裕的读者可以看看原书,写的不错。
安装
Ubuntu, Windows/WSL2, MacOS, 开发者使用的 3 个主流操作系统自带 Python, 无需安装。
如果操作系统上只有 Python2, 可以使用下面的命令安装 Python3。
Ubuntu 和 Windows/WSL2 (针对 Ubuntu 系统) 使用下面的命令安装:
$ sudo apt update && sudo apt install python3
MacOS 使用下面的命令安装:
$ brew install python3
安装完成后,查看版本号:
$ python3 --version
如果希望简化命令,可以利用软连接 (ln 命令) 将 python3
重命名一份为 python
:
# 通过软连接复制 & 重命名 1 个 python 可执行文件
$ sudo ln -s $(which python3) /usr/local/bin/python
# 复制完成后,直接使用 python (执行 python3) 命令即可
$ python --version
# 输出如下
Python 3.12.3
Python 文件执行方法
$ python 文件名称
# 示例 1
python main.py
# 示例 2
python test.py
# 示例 3
python debug.py
Hello World
将下面的代码写入 main.py
文件 (注意顶部的注释部分是必须的):
print("hello world")
使用 Python 命令,执行 Python 文件
$ python main.py
关键字
Python 和其他主流编程语言一样,关键字不能作为变量、函数、方法、任意数据的名称。
Python 中的关键字有:
import keyword
# 高逼格输出 Python 语言关键字
print(keyword.kwlist)
输出如下:
['False', 'None', 'True', 'and', 'as',
'assert', 'async', 'await', 'break', 'class',
'continue', 'def', 'del', 'elif', 'else',
'except', 'finally', 'for', 'from', 'global',
'if', 'import', 'in', 'is', 'lambda',
'nonlocal', 'not', 'or', 'pass', 'raise',
'return', 'try', 'while', 'with', 'yield']
分类
-
控制流:
if
,else
,elif
:条件控制for
,while
,break
,continue
: 循环控制try
,except
,finally
,raise
: 异常处理
-
函数/类:
class
:定义类def
:定义函数lambda
:定义匿名函数return
:从函数返回值yield
:在生成器中返回值
-
逻辑运算符号:
and
,or
,not
,is
,in
-
变量作用域:
global
:声明全局变量nonlocal
:声明使用外部(非全局)变量
-
其他关键字:
import
,from
:导入模块as
:用于导入时的别名with
:上下文管理器pass
:什么也不做assert
:用于调试的断言del
:删除对象async
,await
:异步编程关键字
基本语法格式
- 任意表达式和语句之后不需要加
;
print('hello world')
print('hello world')
print('hello world')
- 条件表达式缩进,函数体缩进不需要加
{ }
, 直接使用tab
制表符缩进即可 (Python 游标卡尺的梗由来),所以要特别注意缩进 (很容易引发 Bug )
while True:
print('hello world')
if 200 > 100:
print('hello world')
if 1 > 0:
print('hello world')
break
- 单行注释使用
#
开头
print('hello world') # output hello world
- 多行注释 (文档) 使用一对
"""
包起来
print('hello world') # output hello world
while True:
"""
something ...
will output `hello world` three times
something ...
"""
print('hello world')
if 200 > 100:
print('hello world')
if 1 > 0:
print('hello world')
break
变量
Python 作为弱类型语言,变量不区分数据类型,并且变量在声明定义之后,可以随时修改其值的数据类型。
# 此时变量 msg 是字符串类型
msg = "Hello World"
print(msg)
# 此时变量 msg 是整型
msg = 100
print(msg)
# 此时变量 msg 是浮点型
msg = 3.14
print(msg)
在上面的代码中可以看到,Python 中变量的数据类型和值可以随时修改。
多个变量赋值时,如果希望代码保持在同一行,使用 ;
分割即可
five = 5; three = 3
print(five, three)
数据类型
限于篇幅,本文只讲解一些常用的数据类型。
特殊值
- None: 表示一个空值或未定义的值
布尔值
Python 中的布尔值首字母是大写,这一点和主流编程语言不同。
- True
- False
整型/浮点型
print(100 + 200)
print(100 - 200)
print(100 * 200)
print(100 / 200)
print(100 ** 3) # ** 表示幂运算符
浮点数和整数略有差异的地方就是: 浮点数存在 精度问题,所以小数的位数可能是不确定的。
print(0.1 + 0.2)
print(0.1 - 0.2)
print(0.1 * 0.2)
print(0.1 / 0.2)
print(0.1 ** 3) # ** 表示幂运算符
执行输出如下:
0.30000000000000004
-0.1
0.020000000000000004
0.5
0.0010000000000000002
通过输出结果可以看到,当小数位数越多越多时,精度就可能会丢失。
类型转换
弱类型语言中的常用功能之一就是:将数字转换为字符串 (或者将字符串转换为数字),然后再进行相应的运算。
通过 str
函数进行强制转换即可。
# 整型转换字符串
print("2 ^ 10 = " + str(1024)) # 2 ^ 10 = 1024
# 浮点型转换字符串
print("π = " + str(3.14)) # π = 3.14
# 字符串转换整型
print("2 ^ 10 = ", int('1024')) # 2 ^ 10 = 1024
# 字符串转换符点型
print("2 ^ 10 = ", float('1024')) # 2 ^ 10 = 1024.0
# 整型转换布尔类型
# 整型不等于 0, True
# 整型等于 0, False
print(bool(1024), bool(0)) # True False
# 字符串转换布尔类型
# 字符串长度不等于 0, True
# 字符串长度等于 0, False
print(bool('1024'), bool('')) # True False
字节
字节(bytes)用于存储二进制数据。
bs = b'Hello, World!'
# 输出 bytes 对应的 ASCII 数字
# 也就是该字节的整数表示
for b in bs:
print(b)
不可变性
字节一旦创建,就不能修改其中的元素。
bs = b'Hello, World!'
bs[0] = 'h' # TypeError: 'bytes' object does not support item assignment
字符串
Python 中的字符串使用双引号或者单引号,直接包起来即可。
print("Hello World")
print('Hello World')
单引号和双引号可以互相包含,灵活运用:
print('Hello"" World')
print("Hello'' World")
使用 \
作为转义符号:
print('Hello World\'')
print("Hello\\ World")
不可变性
字符串一旦创建,就不能修改其中的元素。
msg = 'Hello world'
msg[0] = 'h' # TypeError: 'str' object does not support item assignment
子字符串
直接使用 in
表达式检测字符串中是否包含指定子字符串。
msg = 'hello world'
print('o' in msg) # True
拼接
直接使用 +
即可。
print("hello" + " " + "world")
大小写转换
- title() 将每个单词的首字母大写
- upper() 将字符串中的字母全部转换为大写
- lower() 将字符串中的字母全部转换为小写
msg = "hello world"
print(msg.title()) # Hello World
print(msg.upper()) # HELLO WORLD
print(msg.lower()) # hello world
删除空白字符
msg = " hello world"
print(msg.lstrip())
print(msg.rstrip())
print(msg.strip())
条件表达式
if
语句不需要单独使用 ()
括起来,表达式语句末尾跟随一个 :
分号即可。
比较运算符分为 >
, >=
, <
, <=
, ==
, !=
。
five = 5; three = 3
if five > three:
print(five, " > ", three)
大小写敏感
Python 中比较字符串时大小写敏感,例如,两个大小写不同的值会被视为不相等:
print('hello' == 'Hello') # False
如果希望字符串比较时不区分大小写,可以先全部转换为大写或者小写,然后再比较。
布尔运算符
Python 中的布尔运算符并不是 &&
和 ||
,而是使用单词 and
和 or
这一点和主流编程语言不同。
使用 and
表示条件与操作:
five = 5; three = 3; one = 1
if five > three and five > one:
print(five, " > ", three, " && ", five, " > ", one) # 5 > 3 && 5 > 1
使用 or
表示条件或操作:
hour = 23
if hour >= 22 or hour < 6:
print("Sleeping")
包含运算符
使用关键字 in
表示包含运算关系符,判断指定的值是否存在于列表中。
my_list = [1, 2, 3, 4, 5]
if 100 in my_list:
print("100 in list")
else:
print("100 not in list")
当然,对应的 not in
表示非包含运算关系符。
my_list = [1, 2, 3, 4, 5]
if 100 not in my_list:
print("100 not in list")
else:
print("100 in list")
多行语句
如果 if
条件表示式对应的逻辑包含多行代码语句,保持对应的缩进即可。
my_list = [1, 2, 3, 4, 5]
if 1 in my_list:
print("100 in list")
print("100 in list again")
print("100 in list again")
print("100 in list again")
else:
print("100 not in list")
多个条件语句
当判断条件超过 2 个时,使用 if - else
就无法覆盖到所有条件,这时可以使用 if-elif-else
结构来描述多个逻辑。
score = 80
if score > 96:
print('A+')
elif score > 90:
print('A+')
elif score > 85:
print('B+')
elif score > 80:
print('B')
elif score > 75:
print('C+')
elif score > 70:
print('C')
else:
print('D')
循环表达式
while 循环:
n = 1
while n <= 5:
print(n)
n+=1
转换为等价的 for 循环:
for i in range(1, 6):
print(i)
break
和 continue
和其他语言中的语义一致,这里不再赘述。
配合 else 语句
Python 中提供了一个有趣的特性: 循环语句 + else
语句。
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num == 10:
print("Found 10!")
break
else:
print("10 not found.")
上面的示例代码中,如果 for
循环没有被 break
语句打断,else
子句就会执行。
下面是等价的 while
循环实现代码:
n = 1
while n <= 5:
if n == 10:
print("Found 10!")
break
else:
n+=1
else:
print("10 not found.")
列表
Python 中并没有内置的 数组 数据结构,而是使用 list (列表) 作为常用的数据结构之一,列表支持动态大小的数组,可以包含不同类型的元素。
使用 []
定义即可,每个元素使用 ,
分隔开。
# 整型 list
my_list = [1, 2, 3, 4, 5]
print(my_list)
# 浮点型 list
my_list = [1.1, 2.2, 3.3, 4.4, 5.5]
print(my_list)
# 字符串 list
my_list = ['hello', 'world', 'Python']
print(my_list)
# 拥有不同数据类型的 list
my_list = [100, 3.14, 'hello', 'world']
print(my_list)
获取长度
my_list = [1, 2, 3, 4, 5]
print(len(my_list))
判断是否为空
Python 在列表为空时返回 False,在列表不为空时返回 True。
my_list = []
if my_list:
print("list is not empty")
else:
print("list is empty")
索引访问
my_list = [1, 2, 3, 4, 5]
# 访问第 1 个元素
print(my_list[0])
# 访问最后 1 个元素
print(my_list[-1])
# print(my_list[5]) # 越界错误: IndexError: list index out of range
区间访问
使用两个索引作为区间索引,可以获取位于这两个索引之间的所有元素。
my_list = [1, 2, 3, 4, 5]
# 获取第 2 个元素到第 5 个元素之间的所有元素
print(my_list[1:4]) # [2, 3, 4]
修改元素
my_list = [1, 2, 3, 4, 5]
print(my_list)
my_list[0] = 100
my_list[1] = 200
print(my_list) # [100, 200, 3, 4, 5]
添加元素
1. 在列表末尾追加元素
my_list = [1, 2, 3, 4, 5]
print(my_list)
my_list.append(6)
my_list.append(7)
print(my_list) # [1, 2, 3, 4, 5, 6, 7]
2. 在列表指定位置追加元素
my_list = [1, 2, 3, 4, 5]
print(my_list)
# 在列表第 1 个位置之前插入 0
my_list.insert(0, 0)
# 在列表第 2 个位置之前插入 1.5
my_list.insert(2, 1.5)
print(my_list) # [0, 1, 1.5, 2, 3, 4, 5]
删除元素
1. 删除指定位置的元素
根据列表索引删除:
my_list = [1, 2, 3, 4, 5]
print(my_list)
# 删除第 1 个元素
# 下面两种语法等价
del my_list[0]
#del(my_list[0])
# 此时列表还剩下 4 个元素
# 删除第 1 个元素
del my_list[0] # [3, 4, 5]
# 此时列表还剩下 3 个元素
print(my_list) # [3, 4, 5]
调用 pop
方法删除:
my_list = [1, 2, 3, 4, 5]
print(my_list)
# 删除第 1 个元素
# 删除的元素可以不接收,直接忽略
my_list.pop(0)
# 删除最后 1 个元素
# 删除的元素可以接受,赋值到变量
ele = my_list.pop(0)
print(ele) # 2
print(my_list) # [3, 4, 5]
- 如果删除的元素在下文中需要使用,调用
pop
方法 - 如果删除的元素不再使用,调用
del
方法
2. 删除最后一个元素
直接调用 pop
方法即可。
my_list = [1, 2, 3, 4, 5]
print(my_list)
# 删除最后 1 个元素
# 删除的元素可以不接收,直接忽略
my_list.pop()
# 删除最后 1 个元素
# 删除的元素可以接受,赋值到变量
ele = list.pop()
print(ele) # 4
print(my_list) # [1, 2, 3]
3. 根据值删除元素
直接调用 remove
方法即可。
my_list = [1, 2, 3, 4, 5]
print(my_list)
my_list.remove(1)
my_list.remove(2)
print(my_list) # [3, 4, 5]
需要注意的是,对于列表中的重复值,remove
方法每次只删除第一个值,如果需要全部删除,需要多次调用。
my_list = [1, 1, 3, 4, 5]
print(my_list)
my_list.remove(1)
print(my_list) # [1, 3, 4, 5]
my_list.remove(1)
print(my_list) # [3, 4, 5]
下面是等价实现的循环代码:
my_list = [1, 1, 3, 4, 5]
while 1 in my_list:
my_list.remove(1)
print(my_list) # [3, 4, 5]
排序
- sort() 方法: 永久排序
- sorted() 函数: 临时排序
下面是一个永久排序的小例子:
my_list = [1, 2, 3, 4, 5]
my_list.sort()
# 默认情况下是升序排序
print(my_list) # [1, 2, 3, 4, 5]
# 如果希望倒序排序
# 需要传递参数 reverse=True
my_list.sort(reverse=True)
print(my_list) # [5, 4, 3, 2, 1]
下面是一个临时排序的小例子:
my_list = [1, 2, 3, 4, 5]
# 默认情况下是升序排序
print(sorted(my_list)) # [1, 2, 3, 4, 5]
# 如果希望倒序排序
# 需要传递参数 reverse=True
print(sorted(my_list, reverse=True)) # [5, 4, 3, 2, 1]
反转
直接调用 reverse
方法即可。
my_list = [1, 2, 3, 4, 5]
my_list.reverse()
print(my_list) # [5, 4, 3, 2, 1]
遍历
my_list = [1, 2, 3, 4, 5]
# 注意 for 语句的末尾需要加一个 : 分号
for v in my_list:
print(v * 100)
# 需要注意的是:
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5
等价于下面的代码
for v in range(1, 6):
print(v * 100)
# 需要注意的是:
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5
还有一点需要注意的是: 循环时会将列表中的元素的值,赋值给临时变量,所以即使修改,也不会影响到列表元素值。
my_list = [1, 2, 3, 4, 5]
for v in my_list:
v *= 100
# 需要注意的是:
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5
# 循环中修改的是临时变量 v 的值
# 所以列表元素的值不受任何影响
print(my_list) # [1, 2, 3, 4, 5]
直接转换数字列表
可以通过使用函数 list()
将 range()
的结果直接转换为数字列表。
my_list = list(range(1, 6))
print(my_list) # [1, 2, 3, 4, 5]
# range 函数的第 3 个参数可以指定步长
# 指定生成列表的每次增加 2
my_list = list(range(1, 11, 2))
print(my_list) # [1, 3, 5, 7, 9]
通过组合使用函数 list()
将 range()
, 可以灵活生成各种类型和规则的数字列表。
# 每个数字递增 10
my_list = list(range(10, 51, 10))
print(my_list) # [10, 20, 30, 40, 50]
# 从 0 开始
my_list = list(range(6))
print(my_list) # [0, 1, 2, 3, 4, 5]
# 递减列表
my_list = list(range(5, 0, -1))
print(my_list) # [5, 4, 3, 2, 1]
# 包含负数的列表
my_list = list(range(-3, 3, 1))
print(my_list) # [-3, -2, -1, 0, 1, 2]
计算操作
# 每个数字递增 10
my_list = list(range(6))
print(max(my_list)) # 5
print(min(my_list)) # 0
print(sum(my_list)) # 15
# 检查列表中的所有元素是否都为 True
# 非零数字被视为 True
print(all(my_list)) # False
列表解析操作
在创建列表的同时,就对列表中的元素进行处理,等于是利用语法糖的形式简化了代码实现。
# 1. 生成 1 ~ 5 的数字列表
# 2. 计算列表中每个数字的平方,并使用平方值替换元素值
my_list = [val ** 2 for val in range(1, 6)]
print(my_list) # [1, 4, 9, 16, 25]
上面的代码实现等价于下面的代码:
my_list = [1, 2, 3, 4, 5]
# enumerate(my_list): 生成一个包含索引和值的迭代器
# 每次循环中,index 是当前元素的索引,value 是当前的值
for index, value in enumerate(my_list):
my_list[index] = value ** 2
print(my_list) # [1, 4, 9, 16, 25]
当需要对列表中的所有元素执行相同的处理操作时,使用 列表解析
来简化代码。
切片
所谓切片数据结构,也就是列表的一部分数据。
同时指定左右边界索引,获取第 2 个到第 5 个元素之间的所有元素。
my_list = [1, 2, 3, 4, 5]
print(my_list[1:4]) # [2, 3, 4]
不指定左边界索引,获取第 1 个到第 5 个元素之间的所有元素。
my_list = [1, 2, 3, 4, 5]
print(my_list[:4]) # [1, 2, 3, 4]
不指定右边界索引,获取第 3 个到最后 1 个元素之间的所有元素。
my_list = [1, 2, 3, 4, 5]
print(my_list[2:]) # [3, 4, 5]
索引也可以为负数,表示倒数。
获取列表的最后 3 个数字元素:
my_list = [1, 2, 3, 4, 5]
print(my_list[-3:]) # [3, 4, 5]
获取列表除了最后 3 个元素外的所有其他元素:
my_list = [1, 2, 3, 4, 5]
print(my_list[:-3]) # [1, 2]
获取所有元素:
my_list = [1, 2, 3, 4, 5]
print(my_list[:]) # [1, 2, 3, 4, 5]
复制
和主流编程语言一样,Python 中默认的复制行为也是浅拷贝。
my_list = [1, 2, 3, 4, 5]
print(my_list) # [1, 2, 3, 4, 5]
my_list2 = my_list
print(my_list2) # [1, 2, 3, 4, 5]
my_list2[0] = 100
# 修改列表 my_list2 元素的同时
# 列表 my_list 的元素也受到了影响
# 这不是我们期望的结果
print(my_list) # [100, 2, 3, 4, 5]
print(my_list2) # [100, 2, 3, 4, 5]
如果我们希望复制列表 (深拷贝) ,可以创建一个包含整个列表元素的切片,然后进行赋值操作,就可以完成整个列表的复制 (深拷贝)。
my_list = [1, 2, 3, 4, 5]
print(my_list) # [1, 2, 3, 4, 5]
my_list2 = my_list[:]
print(my_list2) # [1, 2, 3, 4, 5]
my_list2[0] = 100
# 修改列表 my_list2 元素的同时
# 列表 my_list 的元素并没有受到任何影响
print(my_list) # [1, 2, 3, 4, 5]
print(my_list2) # [100, 2, 3, 4, 5]
元组
列表中的元素是可变的 (可以被修改/删除),如果希望列表元素不可变,可以使用元组 (不可变的列表数据结构)。
使用 ()
定义即可,每个元素使用 ,
分隔开。
tuple = (1, 2, 3, 4, 5)
print(tuple) # (1, 2, 3, 4, 5)
# 试图修改时报错
# TypeError: 'tuple' object does not support item assignment
tuple[0] = 100
当然,我们可以利用弱语言的特性,直接修改变量的值,可以达到修改元组元素值的效果。
tuple = (1, 2, 3, 4, 5)
print(tuple) # (1, 2, 3, 4, 5)
tuple = (10, 20, 30, 40, 50)
print(tuple) # (10, 20, 30, 40, 50)
元组是比列表更简单的数据结构,如果需要存储的元素值在程序的整个生命周期内都不变,可以使用元组。
元组和列表的遍历方式一致。
tuple = (1, 2, 3, 4, 5)
for val in tuple:
print(val)
字典
Python 中的字典类似于其他编程语言中的 map, HashTable, 关联数组等数据类型。
使用 {}
定义即可,每个元素使用 ,
分隔开,语法格式和 JSON 很像。
languages_rank = {
'C': 1,
'C++': 2,
'Java': 3,
'Python': 4,
}
print(languages_rank) # {'C': 1, 'C++': 2, 'Java': 3, 'Python': 4}
字典中的每个 Key
和 Value
数据类型都可以不一样。
mixed_dict = {
'C': 1, # string -> int
'C++': 2.0, # string -> float
'Java': '3', # string -> string
4: 'Python' # int -> string
}
print(mixed_dict) # {'C': 1, 'C++': 2.0, 'Java': '3', 4: 'Python'}
访问元素
使用 [key_name]
直接访问即可。
mixed_dict = {
'C': 1, # string -> int
'C++': 2.0, # string -> float
'Java': '3', # string -> string
4: 'Python' # int -> string
}
print(mixed_dict['C']) # 1
# 访问不存在的 Key 时会报错
# print(mixed_dict['C2']) # KeyError: 'C2'
# 防御式编程
# 1. 先检测对应的 key 是否存在于字典
# 2. 然后再访问
if 'C2' in mixed_dict:
print(mixed_dict['C2'])
else:
print("Key C2 not in dict")
# 也可以通过字典提供的 get 方法
# 当指定的 key 不存在时,返回 None
print(my_dict.get('C2')) # None
添加/修改元素
- key 对应的元素存在时,修改原有的值
- key 对应的元素不存在时,添加新值
my_dict = {
'C': 1,
}
print(my_dict) # {'C': 1}
my_dict['C++'] = 2
my_dict['Java'] = 3
print(my_dict) # {'C': 1, 'C++': 2, 'Java': 3}
my_dict['C'] = 100
print(my_dict) # {'C': 100, 'C++': 2, 'Java': 3}
删除元素
my_dict = {
'C': 1,
}
print(my_dict) # {'C': 1}
my_dict['C++'] = 2
my_dict['Java'] = 3
print(my_dict) # {'C': 1, 'C++': 2, 'Java': 3}
# 调用 del 函数删除
# 下面两种语法等价
del my_dict['C++']
del(my_dict['Java'])
print(my_dict) # {'C': 1}
遍历
通过调用 items()
方法将字典转换为一个键值对列表,遍历的时候就可以直接访问当前元素的 key 和值。
my_dict = {
'C': 1,
'C++': 2,
'Java': 3,
'Python': 4,
}
for key, val in my_dict.items():
print("language = ", key, " rank = ", val)
通过调用 keys()
方法获取字典中的所有 key。
my_dict = {
'C': 1,
'C++': 2,
'Java': 3,
'Python': 4,
}
print(my_dict.keys()) # dict_keys(['C', 'C++', 'Java', 'Python'])
# 可以直接通过 list 函数将结果转换为一个列表
print(list(my_dict.keys())) # ['C', 'C++', 'Java', 'Python']
当然,对应的 values()
方法获取的就是字典中的所有值。
my_dict = {
'C': 1,
'C++': 2,
'Java': 3,
'Python': 4,
}
print(my_dict.values()) # dict_values([1, 2, 3, 4])
# 可以直接通过 list 函数将结果转换为一个列表
print(list(my_dict.values())) # [1, 2, 3, 4]
嵌套 (复合) 字典
通过使用基础类型 + 复合数据类型构建的字典。
例如,我们可以定义 1 个排名 Top 10 的编程语言的学习曲线字典。
# 排名数据为模拟,非真实数据
# 真实排名: https://www.tiobe.com/tiobe-index/
my_dict = {
'name': 'Language learning curve',
'desc': 'The learning difficulty curve of the top 10 languages',
'year': 2024,
'month': 8,
'source': 'fake data',
'langs': ['C', 'C++', 'Java', 'Python', 'Go', 'Rust', 'Ruby', 'Javascript', 'SQL', 'C#'],
'ranks': {
'C': 1,
'C++': 2,
'Java': 3,
'Python': 4,
'Go': 5,
'Rust': 6,
'Ruby': 7,
'Javascript': 8,
'SQL': 9,
'C#': 10,
}
}
# 输出编程语言数量
print("languages count is ", len(my_dict['langs']))
# 输出每个编程语言的排名
for lang in my_dict['langs']:
# 格式化输出,需要 Python 3.6 及以上版本
# f"{lang:10s}" 表示编程语言名称,占 10 个字符串的位置
# f"{my_dict['ranks'][lang]:2d}" 表示编程语言排名,占 2 个数字的位置
print(f"{lang:10s}", f"{my_dict['ranks'][lang]:2d}")
# 输出如下
# languages count is 10
# C 1
# C++ 2
# Java 3
# Python 4
# Go 5
# Rust 6
# Ruby 7
# Javascript 8
# SQL 9
# C# 10
从上面的实例代码可以看到,Python 的字典和 JSON 的语法格式几乎一样。
key 的类型
Python 中,字典的 key 必须是 不可变 的数据类型。
因为只有值不可变,才会有固定的哈希值,通过使用不可变类型作为字典 key,才能确保 key 在字典中的唯一性和稳定性。
可以作为 key 的数据类型
- 整型
- 浮点型
- 字符串
- 布尔值
- 元组
- frozenset (不可变集合)
my_dict = {
1: 1, # 整型 key
1.23: 4.56, # 浮点型 key
'one': '1', # 字符串 key
True: 1, # 布尔值 key
(1, 2, 3): [1, 2, 3], # 元组类型 key
frozenset([1, 2, 3]): [1, 2, 3] # frozenset key
}
print(my_dict) # {1: 1, 1.23: 4.56, 'one': '1', (1, 2, 3): [1, 2, 3], frozenset({1, 2, 3}): [1, 2, 3]}
不可以作为 key 的数据类型
- 列表
- 字典
- 集合
- 用户自定义类型
列表类型作为 key 报错:
my_dict = {[1, 2]: 'list key'}
# print(my_dict) # TypeError: unhashable type: 'list'
字典类型作为 key 报错:
my_dict = {{'key': 'value'}: 'dict key'}
print(my_dict) # TypeError: unhashable type: 'dict'
集合类型作为 key 报错:
my_dict = {{1, 2}: 'set key'}
print(my_dict) # TypeError: unhashable type: 'set'
集合
集合 (Set) 类似于列表数据类型,但每个元素都必须是唯一的。
‼️重要: Set 输出时时无序的。
直接将列表类型转化为集合 (去重):
my_list = [1, 2, 3, 4, 5, 1, 1]
print(set(my_list)) # {1, 2, 3, 4, 5}
直接将字典类型转化为集合 (去重):
my_dict = {
'C': 1,
'C': 100,
'C++': 2,
'Java': 3,
'Python': 4,
}
# Set 输出时时无序的
# 所以你的输出和这里的输出不一样,属于正常现象
print(set(my_dict)) # {'Java', 'Python', 'C', 'C++'}
数学运算
Python 集合的强大之处就是可以直接通过 布尔运算符
进行数学计算。
s1 = set([1, 2, 3, 3, 3])
s2 = set([3, 3, 3, 4, 5])
print(s1) # {1, 2, 3}
print(s2) # {3, 4, 5}
print(s1 | s2) # 并集 {1, 2, 3, 4, 5}
print(s1 & s2) # 交集 {3}
print(s1 - s2) # 差集 {1, 2}
不可变集合
定义关键字: frozenset
fs = frozenset([1, 2, 3, 4, 5])
fs.add(6) # AttributeError: 'frozenset' object has no attribute 'add'
函数
定义关键字: def
。
def say_hello():
print('Hello World')
say_hello()
参数
可以传递 0 - N 个参数,除此之外,还可以设置参数默认值。
def say_hello(name):
print('Hello', name.title())
say_hello('Tom') # Hello Tom
因为参数设置了默认值,所以调用函数时即使不传入参数,也可以正常输出。
def say_hello(name='Tom'):
print('Hello', name.title())
# 不传入参数,以默认值为准
say_hello() # Hello Tom
# 传入参数,以参数值为准
say_hello('world') # Hello World
关键字实参
默认情况下,函数参数的传入顺序和声明顺序一一对应,但是对于拥有多个参数的函数,如果我们希望调用函数时只传入一个或某几个参数,这时就可以使用 关键字实参
的传入规则了。
‼️重要: 使用关键字实参时,确保准确地指定函数定义中的参数名称。
def say_hello(first_name, last_name):
print('Display name:', first_name, last_name)
say_hello(first_name='Tom', last_name='Jerry') # Display name: Tom Jerry
say_hello(last_name='Tom', first_name='Jerry') # Display name: Jerry Tom
返回值
def say_hello(first_name, last_name):
return first_name + ' ' + last_name
print(say_hello(first_name='Tom', last_name='Jerry')) # Tom Jerry
print(say_hello(last_name='Tom', first_name='Jerry')) # Jerry Tom
任意可变数量参数
定义: 参数名称前加 *
即可,表示 0 到 N 个参数。
def output(*langs):
print(langs)
output() # ()
output('C') # ('C',)
output('C', 'C++') # ('C', 'C++')
output('C', 'C++', 'Python') # ('C', 'C++', 'Python')
可以使用 for
循环遍历可变参数列表:
def output(*langs):
for lang in langs:
print(lang)
可变数量参数必须位于其他参数的后面。
def output(title, desc, *langs):
print(title, desc, langs)
任意可变数量关键字参数
定义: 参数名称前加 **
即可,表示 0 到 N 个已命名的参数。
利用这个特性可以将命名参数直接映射到函数中。
def build(first, last, **profile):
"""
第 3 个参数为 可变数量关键字参数
包含了用户的其他额外信息
"""
user = {}
user['first_name'] = first
user['last_name'] = last
for key, val in profile.items():
user[key] = val
return user
# 前 2 个参数使用实际参数
# 第 3 个参数使用关键字参数
albert = build('albert','einstein',
location='princeton',
field='physics')
print(albert) # {'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}
可变参数和不可变参数类型
前面讲字典的 key 的类型时,提到了:
Python 中的数据类型分为可变类型和不可变类型。
这条规则对于函数参数依然适用:
- 可变类型的数据传入函数后并被修改,会改变原数据
- 不可变类型的数据则传入函数后并被修改,不会改变原数据
Python 中不能直接操作指针,也没有显式的指针特性,所以无法直接通过指针来修改不可变类型的值。
不可变类型的本质:作为函数参数传递时不会创建新的对象 (浅拷贝),避免内存开销,从而提升性能。
不可变类型的本质:作为函数参数传递时会创建新的对象 (深拷贝),而不是原对象的引用 (指针)。
当然,对于必须要修改不可变类型值的场景,可以使用 “迂回” 的方法来解决:
- 通过返回新值覆盖不可变类型变量
- 通过将 不可变类型 放入 可变类型 (容器) 修改不可变类型
下面是一个 不可变类型
的例子。
def double(n):
n *= 2
# 整型是不可变类型
n = 1
# 即使函数内部改变参数值,不会改变原数据值
double(n)
print(n) # 1
下面是一个 可变类型
的例子。
def double(numbers):
for i in range(len(numbers)):
numbers[i] *= 2
# 列表、字典、集合、用户自定义类型 都是可变类型
my_list = [1, 2, 3, 4, 5]
# 函数内部改变参数值,会改变原数据值
double(my_list)
# 此时,原始数据已经发生变化
print(my_list) # [2, 4, 6, 8, 10]
# 如果我们原数据不会被改变
# 传入列表的复制 (深拷贝) 即可
my_list2 = [1, 2, 3, 4, 5]
double(my_list2[:])
print(my_list2) # [1, 2, 3, 4, 5]
递归
# 使用递归函数计算 fibonacci 数列
def fib(n):
if n < 2:
return 1
if n == 2:
return 1
else:
return fib(n-1) + fib(n-2)
# [1, 1, 2, 3, 5, 8]
print(fib(6)) # 8
类和面向对象
Python (约定俗成,并非强制规定) 类名应采用 (首字母大写的) 驼峰命名法,类的属性和方法使用下划线命名法。
类的方法如果需要传递参数,那么第一个参数必须是 self
(语言强制规定)。
- 在 类 中使用一个空行来分隔 方法
- 在 模块 中使用两个空行来分隔 类
# 定义一个类: 人类
class Person:
"""
类的静态字段
如果需要定义属于类本身的字段(而不是具体实例的字段)
可以直接在类中定义,这些字段被称为类字段或 静态字段
静态字段被类的所有实例共享
需要注意的是,静态字段可以被修改
但是修改后,只影响修改的实例,其他实例不受影响
"""
# 所有人类共同的家园: 地球
home = "Earth"
"""
初始化方法
相当于其他编程语言中的类的构造方法
类的每个方法的第一个参数必须是 self
"""
def __init__(self, name) -> None:
"""
初始化属性
Python 类的属性字段不需要在 class 类中显式声明和定义
直接在 __init__ 方法中动态创建和初始化即可
"""
self.name = name
self.age = 3
"""
定义方法 1
类的每个方法的第一个参数必须是 self
self 用于指向实例本身
"""
def sayHi(self):
print('Hi, My name is', self.name)
"""
定义方法 2
类的每个方法的第一个参数必须是 self
self 用于指向实例本身
"""
def sleep(self):
print('Sleeping ...')
"""
定义方法 3
类的每个方法的第一个参数必须是 self
self 用于指向实例本身
"""
def set_age(self, age):
self.age = age
# 定义人类的两个具体实例
p1 = Person('Alice')
p2 = Person('Bob')
# 输出两个实例的静态字段
print(p1.home) # Earth
print(p2.home) # Earth
# 修改实例 1 的静态字段
# 实例 1 的静态字段不受任何影响
p1.home = "Mars"
print(p1.home) # Mars
print(p2.home) # Earth
# 调用实例的方法
p1.sayHi() # Hi, My name is Alice
p2.sayHi() # Hi, My name is Bob
# 访问不存在的属性时会报错
# print(p1.hobby) # AttributeError: 'Person' object has no attribute 'hobby'
修改属性值
1. 直接修改
p1.age = 10
print(p1.age) # 10
2. 通过方法修改
p1.set_age(10)
print(p1.age) # 10
公共/私有作用域
Python 中,类的属性和方法默认是公开的(public), 和主流编程语言使用关键字(public/protected/private)进行访问控制不同,Python 通过命名约定来定义公开、受保护、私有的属性和方法,但是 受保护机制
并不是语言强制规定的,而是约定俗成的规定,所以即使违反规则,也并不会产生运行时错误。
- 公开: 没有下划线前缀的属性和方法,任何地方都可以访问
- 私有: 以单下划线
_
开头的属性和方法,仅在类内部可以访问 - 受保护: 以双下划线
__
开头的属性和方法,仅在类内部及子类中可以访问
class ClassName:
...
下面是一个示例代码。
class Person:
"""
初始化方法
"""
def __init__(self, name) -> None:
"""
初始化属性
"""
self.name = name
# 公开属性
self.age = 3
# 受保护属性
self._salary = 100
# 私有属性
self.__id = 1234567890
# 公开方法
def sayHi(self):
print('Hi, My name is', self.name)
# 受保护方法
def _get_salary(self):
print("This is a protected method")
# 私有方法
def __get_id(self):
print("This is a private method")
# 通过公开访问访问私有属性/方法
def get_id(self):
self.__get_id()
# 定义人类的两个具体实例
p1 = Person('Alice')
# 公开属性/方法 在任何地方都可以访问
print(p1.age)
p1.sayHi()
# 受保护属性/方法 也可以正常访问
# 所以说,只是约定俗成的 (软) 规范
print(p1._salary) # 100
p1._get_salary() # This is a protected method
# 私有属性/方法 只能在类内部访问
# 是 Python 语言的强制性规定
# 在类外部访问时直接报错
# print(p1.__id) # AttributeError: 'Person' object has no attribute '__id'
# p1.__get_id() # AttributeError: 'Person' object has no attribute '__get_id'
# 但是通过 公有方法 访问 私有属性/方法 是 OK 的
p1.get_id() # This is a private method
# 除此之外,Python 提供了一种名称重整(name mangling)机制
# Python 会将私有的属性和方法的名称修改为 _ClassName__attributeName 的形式
# 可以通过 _ClassName__attributeName 的形式来访问类的私有属性/方法
# 这种做法通常不推荐,因为它违背了私有属性和方法的设计初衷
# 访问私有属性,可以正常运行,但是不推荐
print(p1._Person__id) # 1234567890
# 调用私有方法,可以正常运行,但是不推荐
p1._Person__get_id() # This is a private method
继承
Python 和其他主流编程语言中的继承机制规则一样:
- 一个类继承另一个类时,它将自动获得另一个类的所有属性和方法
- 原有的类称为父类,而新类称为子类
- 子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法
基本语法格式:
class SubClass(ParentClass):
...
下面是一个示例代码。
# 定义父类: 动物
class Animal:
"""
父类初始化方法
"""
def __init__(self, name) -> None:
# 父类初始化属性
self.name = name
# 公开方法
def speak(self):
print(f"{self.name} makes a sound")
# 定义子类: 猫
class Cat(Animal):
"""
子类初始化属性
"""
def __init__(self, name) -> None:
# 子类初始化父类的属性
# 关键字 super() 表示父类
super().__init__(name)
# 子类初始化自己的属性
self.age = 3
self.friends = ['Jerry']
"""
子类定义自己的方法
"""
def sayAge(self):
print(f"I'm {self.age} years old")
"""
子类重写 (Overriding) 父类的方法
"""
def speak(self):
print(f"My name is {self.name}")
# 实例化一个父类
duck = Animal('Duck')
duck.speak() # self.age
# 实例化一个子类
tom = Cat('Tom')
tom.speak() # My name is Tom
tom.sayAge() # I'm 3 years old
多重继承
Python 支持多重继承,一个类可以继承多个父类,多个父类的类名在子类的定义中用逗号分隔。
基本语法格式:
class SubClass(ParentClass1, ParentClass2 ...):
...
下面是一个示例代码。
class A:
def method_a(self):
print("Method A")
class B:
def method_b(self):
print("Method B")
class C(A, B):
def method_c(self):
print("Method C")
c = C()
c.method_a() # Method A
c.method_b() # Method B
c.method_c() # Method C
多态
多态机制: 不同的类/实例,调用相同的方法,得到不同的结果。
Python 中的多态的实现基于继承和方法重写(Overriding), 不同的类实现了同样的方法名,在调用这些方法时,会根据对象的实际类型执行对应的方法。
# 定义父类 (基类)
class Animal:
# 基类中定义一个通用方法
def speak(self):
pass
# 定义子类 (狗)
class Dog(Animal):
def speak(self):
print('Woof!')
# 定义子类 (猫)
class Cat(Animal):
def speak(self):
print('Meow!')
# 定义子类 (牛)
class Cow(Animal):
def speak(self):
print('Moo!')
# 函数不关注 参数类型具体属于哪个类
# 直接调用其方法
def animal_sound(animal):
animal.speak()
# 创建不同类型的实例
dog = Dog()
cat = Cat()
cow = Cow()
# 不同的类/实例,调用相同的方法,得到不同的结果
animal_sound(dog) # Woof!
animal_sound(cat) # Meow!
animal_sound(cow) # Moo!
抽象类/方法
Python 中提供了 abc
模块 (名字就是这么随意~) 来定义抽象类,确保子类必须实现某些方法。
from abc import ABC, abstractmethod
# 父类继承自抽象类 ABC
class Animal(ABC):
"""
使用 @abstractmethod 装饰器标记 speak 方法为抽象方法
所有子类必须实现这个方法
"""
@abstractmethod
def speak(self):
pass
# 定义子类 (狗)
class Dog(Animal):
def speak(self):
print('Woof!')
# 定义子类 (猫)
class Cat(Animal):
def speak(self):
print('Meow!')
# 定义子类 (牛)
class Cow(Animal):
def speak(self):
print('Moo!')
# 函数不关注 参数类型具体属于哪个类
# 直接调用其方法
def animal_sound(animal):
animal.speak()
# 创建不同类型的实例
dog = Dog()
cat = Cat()
cow = Cow()
# 不同的类/实例,调用相同的方法,得到不同的结果
animal_sound(dog) # Woof!
animal_sound(cat) # Meow!
animal_sound(cow) # Moo!
静态方法
静态方法可以通过类、实例直接访问。
# 定义父类 (基类)
class Animal:
# 基类中定义一个通用方法
def speak(self):
pass
"""
使用 @staticmethod 装饰器标记 help 方法为静态方法
"""
@staticmethod
def help():
print('help is static method')
# 定义子类 (狗)
class Dog(Animal):
def speak(self):
print('Woof!')
Animal.help() # help is static method
# 创建实例
dog = Dog()
dog.help() # help is static method
异常
Python 支持通过异常处理程序运行时发生的错误。
关键字: try
, except
, finally
# 除数不能为 0
# print(1/0) # ZeroDivisionError: division by zero
# 如果 try 代码块中的代码正常运行
# 跳过 except 代码块
# 如果 try 代码块中的代码运行发生错误
# 执行 except 代码块的代码
# 无论 try 和 except 是否发生运行异常
# finally 代码块中代码 (最后) 都会执行
try:
print(1/0)
except ZeroDivisionError:
print('divide by zero')
finally:
print('finally ...')
Python 还支持异常处理和 else
逻辑表达式组合使用,try
代码块成功执行后的下文代码,都应放到 else
代码块中。
# 如果 try 代码块中的代码正常运行
# 跳过 except 代码块
# 执行 else 代码块中的代码
# 如果 try 代码块中的代码运行发生错误
# 执行 except 代码块的代码
# 无论 try 和 except 是否发生运行异常
# finally 代码块中代码 (最后) 都会执行
try:
answer = 10 / 3
except ZeroDivisionError:
print('divide by zero')
else:
# 如果可以正常计算,则输出
print(answer)
finally:
print('finally ...')
模块
Python 的模块系统非常灵活,可以是一个文件,也可以是多个文件,具体取决于模块的实现方式。
模块导入关键字: import
。
单个文件的模块
最常见的情况是,一个 Python 模块就是一个以 .py
结尾的单个文件。
这个文件中包含了函数、类、变量等 Python 代码,模块名与文件名一致(不包括 .py
扩展名)。
- 假设当前执行文件为
main.py
- 在同级目录,新增一个模块文件
hello.py
将下面的代码写入到 hello.py
文件中:
def say():
print('hello world')
将下面的代码写入到 main.py
文件中:
import hello
hello.say() # hello world
多个文件的模块
如果模块由多个文件组成,这个模块被称为 包。
包是一个包含多个模块的目录,并且这个目录中通常包含一个特殊的 __init__.py
文件,这个文件可以是空的,也可以包含初始化包时运行的代码 (导入时执行)。
假设有以下包 (多个模块) 目录结构:
person
├── __init__.py
├── act.py
└── profile.py
上述目录结构中,person
是 1 个包,它包含了 2 个模块 act
和 profile
。
# __init__.py
print('package init')
# act.py
def say(name):
print("Hi, I'm", name.title())
def run():
print('Running')
1. 导入整个包
import person
可以将整个包导入,如果 __init__.py
文件中定义了初始化代码,会直接执行。
2. 导入单个模块
语法格式如下:
from package_name import module_name
将下面的代码写入到 main.py
文件中:
from person import act
act.say('Tom')
3. 导入单个模块的指定函数
语法格式如下 (可以导入 1 个或多个方法):
from package_name.module_name import function_name_1, function_name_2 ...
将下面的代码写入到 main.py
文件中:
from person.act import say
say('Tom')
4. 使用 as 为函数指定别名
当一个包中的多个模块中存在相同的方法名时,可以使用 as
关键字为同名方法指定别名。
语法格式如下:
from package_name.module_name import function_name as function_name
将下面的代码写入到 main.py
文件中:
from person.act import say as speak
speak('Tom')
5. 导入类相关
导入类和导入函数语法基本一致,具体实例代码不再一一赘述。
这里以包 (模块由多个文件组成) 为示例进行语法说明。
假设有以下包 (多个模块) 目录结构:
animal
├── __init__.py
├── animal.py
├── cat.py
└── dog.py
# 导入 cat 模块中的所有类
from animal.cat import *
# 导入 cat 模块中的 Cat 类
from animal.cat import Cat
# 导入 cat 模块中的 Cat, Cat2 类
from animal.cat import Cat, Cat2
‼️需要注意的是:类和函数一样,是模块导入的最小单位。
单元测试
Python 提供一个单元测试库 unittest
。
- 测试通过时输出 1 个
.
- 测试错误时输出 1 个
E
- 测试失败时输出 1 个
F
新建 2 个文件,calc.py
, test_calc.py
- calc.py: 模块代码文件
- test_calc.py 模块单元测试代码文件
# 目录结构如下
.
├── calc.py
└── test_calc.py
将以下代码写入 calc.py
文件中:
def add(x, y):
return x + y
def multi(x, y):
return x * y
将以下代码写入 test_calc.py
文件中:
import unittest
import calc
""""
TestCalc 类继承自 unittest.TestCase 类
专门用于测试 calc 模块中的函数
每个测试方法都以 test_ 前缀开头
"""
class TestCalc(unittest.TestCase):
"""
测试 add 函数
"""
def test_add(self):
self.assertEqual(calc.add(1, 2), 3)
"""
测试 add 函数
"""
def test_multi(self):
self.assertEqual(calc.multi(1, 2), 2)
"""
unittest.main() 方法执行当前模块中所有测试用例
"""
if __name__ == '__main__':
unittest.main()
执行单元测试代码:
# 测试文件中的所有模块
$ python test_calc.py
# 输出如下
# 2 个测试方法通过,输出 2 个 .
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
除了测试所有模块外,也可以测试指定的模块/测试类/测试函数。
# 指定测试某个模块
$ python -m unittest calc_test
# 指定测试某个测试类
$ python -m unittest calc_test.TestCalc
# 指定测试某个测试类的方法
$ python -m unittest calc_test.TestCalc.test_add
测试钩子函数
import unittest
import calc
""""
TestCalc 类继承自 unittest.TestCase 类
专门用于测试 calc 模块中的函数
每个测试方法都以 test_ 前缀开头
"""
class TestCalc(unittest.TestCase):
"""
测试 add 函数
"""
def test_add(self):
self.assertEqual(calc.add(1, 2), 3)
"""
测试 add 函数
"""
def test_multi(self):
self.assertEqual(calc.multi(1, 2), 2)
@classmethod
def setUpClass(calc):
print('所有测试用例开始前执行')
@classmethod
def tearDownClass(calc):
print('所有测试用例结束后执行')
def setUp(self):
print('单个测试用例开始前执行')
def tearDown(self):
print('单个测试用例结束后执行')
"""
unittest.main() 方法执行当前模块中所有测试用例
"""
if __name__ == '__main__':
unittest.main()
执行单元测试代码:
# 测试文件中的所有模块
$ python test_calc.py
所有测试用例开始前执行
单个测试用例开始前执行
单个测试用例结束后执行
.单个测试用例开始前执行 # 测试通过输出 .
单个测试用例结束后执行
.所有测试用例结束后执行 # 测试通过输出 .
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
API 速查表
在线: https://overapi.com/python