jk's notes
  • 教程

教程

2025年9月16日10:43:07

python3.13 的官方教程

本教程是一个整体教程, 并非包含所有细节. 并且不是编程的入门教程, 只是 python 的入门教程.

1. 课前甜点

Python 是解释性程序, 对于短小的任务, 可以更为快速的处理.

如果不厌烦类型, 编译, 甚至是 IDE 等繁琐的操作, 其实用 C++ 等其他高级语言也一样. 个人习惯 JavaScript(Node), 也可以处理日常的小东西, 但是 Python 的生态实在是太强大了. 特别是 C 开发的库, Node 使用 C 开发的库, 有些需要本地 node-gyp 来构建, 易用上不如 Python. 主要是 Numpy 太强大.

另外 Python 的命名一开始并不来自蟒蛇. 它源自 BBC 的 "Monty Python 飞行马戏团".

2. 使用 Python 解释器

2.1. 换出解释器

首先介绍怎么找 python 的安装目录 (略).

运行 python 命令后进入交互式解释器. 退出使用 quit(). 其他方式可以忽略.

一些特殊的启用解释器的方式:

  • python -c "命令" 执行字符串.
  • python -m "模块" 可以执行模块.

2.1.1. 传入参数

要获得命令行参数, 使用 sys 模块下的 argv 列表.

# index.py
import sys
for v in sys.argv:
    print(v)

需要注意的是:

  • python 解释器会忽略 -c, -m 之后的内容, 其内容会交给执行的内容去处理.
  • python 文件.py 命令会将 python 后的所有都放到 sys.argv 中.
  • python -c 命令, 其中 sys.argv[0] 就是 -c, 需要注意的是会忽略命令本书.
  • python -m 模块名, 其中 sys.argv[0] 是模块的全路径.
python -c 'import sys; print(sys.argv)' 1 2 a b
# => ['-c', '1', '2', 'a', 'b']

python -m index 1 2 a b
# => 
# C:\Users\jk\Desktop\py-demo\index.py
# 1
# 2
# a
# b

python .\index.py 1 2 a b
# => 
# .\index.py
# 1
# 2
# a
# b

2.1.2. 交互模式

执行 python 后进入交互模式, 终端会显示 >>> 所谓提示符.

使用 quit() 退出.

2.2. 解释器的运行环境

2.2.1. 源文件字符编码

默认会使用 utf-8 编码, 如需要跟换, 在脚本第一行使用

# -*- coding: 编码 -*-

例如, 使用 Windows-1252 编码

# -*- coding: cp1252 -*-

若 Linux 上使用 shebang, 可以写成

#!/usr/bin/env python3
# -*- coding: cp1252 -*-

3. Python 速览

python 使用 # 作为行注释的开头.

3.1. Python 用作计算器

将 Python 解释器当做强大的计算器来使用.

好像很多解释性语言都支持, 比如 node.

3.1.1. 数字

  • 解释器会自动判断数字的字面量, 将其作为 int 或 float 来使用. 混合运算会自动转换为浮点数. 这一点与传统的 C 语言一样.
  • 数字运算使用: 加 (+), 减 (-), 乘 (*), 除 (/, 浮点数除法), 整除 (//), 乘幂 (**). 优先级与普通认知一样.
  • 使用圆括号可以提升优先级.
  • 使用等号来赋值 =. 使用变量存储结果. 变量不用声明, 直接使用 (弱类型). 未赋值的变量无法使用.
  • 交互模式下具有记忆, 可以使用前一个步骤中的变量来进行计算 (有点 matlab 解释器的既视感).

建议交互模式下, 将变量当做只读的来用. 可以避免很多错误.

Python 支撑的类型很多, 例如: decimal, fraction, 甚至是复数.

3.1.2. 文本

文本, 即字符串. 使用单引号, 或双引号括起来.

  • 与其他语言一样, 使用斜线得到转义字符串.
  • 默认字符串是不允许跨行的, 使用三引号得到可以跨行的字符串. 跨行字符串中会在内行嵌入 \n, 如果需要取消将 \ 插入在换行前.
  • 字符串可以使用乘法, "a" * 3 得到 "aaa".
  • 连接字符串使用加号 (+).
  • 两个相邻的字符串会自动合并. 例如: "a" 'b' "c" 逻辑上是 "abc". 在超长字符串分行书写时很有用 (不用再添加加号). 注意只能是字符串字面值.
  • 字符串使用下标索引来访问单个字符, python 是 base-0 的索引规则. 支持负索引, -1 表示最后一个字符. 需要注意 -0 与 0 等价.
  • 字符串也支持切片. 索引语法: str[start:end:step]
    • 采用左闭右开的区间来获取子字符串.
    • step 是步长, 默认为 1, 即后一个取值的索引值与前一个取值的索引值的差.
    • 左右边界可省略, start 默认为 0, end 默认为字符串长度 (取不到, 注意逻辑上不是 -1).
    • 字符串是不可变的 (与其他编程语言一样), 索引越界会报错.
  • 获得字符串长度使用 len(str).

3.1.3. 列表

列表是复合数据类型, 可以存储多种类型的数据.

  • 使用方括号界定.
  • 使用逗号分隔.

注意列表可以存储不同的类型, 只是通常在使用时会尽可能使用同一种类型.

  • 与字符串一样, 列表也支持切片.

  • 列表使用加号, 可以将两个列表合并.

  • 列表是可变的 (字符串不可变), 可以修改对应索引处的内容, 例如: list[index] = value.

  • 列表是对象, 使用 append(value) 方法可以在列表尾部追加元素.

  • 列表是引用类型. 使用切片可以实现浅拷贝. 例如: newList = oldList[:].

  • 切片赋值, 可以修改列表结构. 例如 list[s:e] = [] 相当于清空 [s,e] 处的内容. JS 中也有类似的方法, 例如 splice, 区别在于 splice 的第三个参数是展开的, 不是数组.

  • 使用 len(list) 获得列表长度.

  • 列表的元素可以是任意类型, 也可以是列表.

  • 可以使用 in 运算符判断一个值是否在列表中.

3.2. 走向编程的第一步

文档给出了一个示例, 计算 Fibonacci 数列.

a, b = 0, 1
while a < 10:
    print(a)
    a, b = b, a + b

这段代码非常巧妙的适用了 Python 的语法特性:

  • 首先介绍了 while 循环的结构, 注意冒号, 缩进.
  • 介绍了关系运算符. 关系运算符与其他语言类似: ==, >, <, !=, >=, <=.
  • 特殊的赋值运算.
  • print() 可以接收多个参数, 会将多个参数使用空格分隔进行输出.
  • 默认 print() 会在结尾处使用 \n 换行, 可以替换掉: print(..., end = ',').

4. 更多流程控制

while 循环:

while boolean 表达式:
    循环体

4.1. if 语句

语法:

if 表达式:
    语句
elif 表达式:
    语句
else:
    语句

python 没有 switch, 取而代之的是 match 语句.

4.2. for 语句

不同于其他语言, for 仅仅是按照顺序将列表中的元素一个个取出来.

for 迭代变量 in 列表:
    循环体

如果遍历的是键值对 (花括号分隔, 键值用冒号分隔, 键需要使用引号括起来, 键值对用逗号分隔, 格式如同 JSON), 那么迭代的是键.

4.3. range() 函数

用于生成序列, 参数与切片参数一样, 采用左闭右开区间来生成. 默认步长为 1

  • 需要注意 range() 生成的是 range 类型的数据. 但可以直接 for 遍历.
  • 使用 list() 可以将其转换为列表类型.
  • 参数支持负数.

可以借助 range() 生成索引序列, 通过遍历序列来遍历列表:

for i in range(len(list)):
    操作 list[i]

range 不是真正的列表, 但是使用起来很像列表, 它有点 C# 中 Linq 延迟执行的味道. python 中也有类似于 sun() 等这类直接迭代求值的方法.

4.4. break 和 continue 语句

与其他编程语言一样.

4.5. 循环的 else 语句

与 for 和 while 配对的 else: 语句. 逻辑上是, 如果循环正常结束, 就会执行 else: 中的代码. 这个不正常结束就是 break, return 等.

4.6. pass 语句

放在空结构中, 逻辑上相当于占位. 由于 python 使用缩进来描述一个逻辑块, 当逻辑块是空的时候使用 pass 来充当. 常用与循环, 函数内等.

4.7. match 语句 (*)

逻辑上就是其他语言中的 switch 语句. 但是更为强大. 猜测 C# 中的 模式匹配就是参考它的.

语法:

match 表达式:
    case 匹配项:
        语句
    case 匹配项:
        语句
    case _:
        默认语句

注意, 默认 match 不会像 switch 那样穿透, 匹配到了只会执行那一项的代码.

匹配项很强大:

  • 可以是简单字面量, 如数字, 字符串等.
  • 可以是使用 | 连接的多个字面量, 描述为或者.
  • 可以是元组 (括号括起来, 使用逗号分隔). 在元组中只是使用变量, python 会对数据解包, 并赋值给变量.
  • 可以使用类来组织匹配, 定义好类的构造函数与参数. 利用构造函数与参数来匹配.
  • 在匹配后, 冒号前, 可以使用 if 作为守卫, 在匹配上后, 满足该判断才会进入. 可以结合解包变量赋值来运用.

这个 match 的适用很灵活, 应该注意.

细节可以参考: https://docs.python.org/zh-cn/3.13/tutorial/controlflow.html#match-statements

4.8. 定义函数

语法:

def 函数名(参数列表):
    """文档注释"""
    函数体
  • 函数内部使用局部变量符号表. 即默认都是局部变量.
  • 函数名也可以赋值, 使用另一个名字来调用函数.
  • 函数默认返回值是 None, 要显式返回使用 return.
  • 函数与方法的定义语法是一致的.

4.9. 函数定义详解

函数定义支持可变参数, 这里有三种可以进行组合使用.

4.9.1. 默认值参数

与其他编程语言一样, 定义时使用赋值语句给定默认值, 调用时不传参可以使用默认值, 传参使用传入值. 默认值必须在参数末尾处.

语法与使用与其他语言没什么不同

默认值在 Python 中有点像全局静态变量的感觉. 特别是引用类型. 例如

def f(a, L = []):
    L.append(a)
    return L
print(f(1)) # => [1]
print(f(2)) # => [1,2]
print(f(3)) # => [1,2,3]

给人感觉就好像是一个全局静态字段引用了列表一样. 每次的操作实际上都是对这个列表进行处理. 要避免这个问题可以使用:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

4.9.2. 关键字参数

关键字参数如同其 C# 中的命名参数.

  • 可以不参数定义顺序的限制, 可以直接使用 name=value 的形式传参.
  • 函数调用时, 具名参数与普通参数可以同时存在, 但具名参数必须在后面.

定义函数时, 还有两个特殊的参数形式:

  1. *arguments, 一个前置星号. 接收任意个简单参数, 然后装包成一个元组, 逻辑上与 C# 的 params 参数一样. 注意传入一个元组是只会视为一个元素 (这一点与 C# 不同).
  2. **keywords, 两个前置星号. 接收任意个键值对, 即 key=value 形式的参数. 然后装包成键值对. 注意, 直接传入键值对会报错.

定义函数时, 这两个参数可以同时出现, 但是 键值对 必须放在 元组 的后面.

如果传入参数时就是一个元组或键值对, 需要对应上 *args, **kv 参数, 那么就需要解包后传参. 例如:

def func(**kv):
  for k in kv:
    print(k + '=>' + str(kv[k]))

func(**{
  "name": "jim",
  "age": 19
})

4.9.3. 特殊参数

就是在定义函数的时候, 显式的给定两个标记, 表示怎么传递参数.

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        位置或关键字   |
        |                                - 仅限关键字
         -- 仅限位置
  • / 前的参数必须使用位置传参, 不允许使用关键字传参
  • / 与 * 之前的参数, 既可以用位置, 也可以用关键字的方式传参
  • * 之后的参数必须使用关键字方式传参, 不得使用位置方式传参

逻辑上, 下面两个函数定义是等价的.

def f1(a, b, c):
    pass
def f2(/, a, b, c, *):
    pass

只允许位置传参的函数

def f(a, b, c, /):
    pass

只允许键值对传参的函数

def f(*, a, b, c):
    pass

4.9.4. 任意实参列表

实际上就是 *args 参数. 需要说明的是, 该参数后只允许有关键字参数.

4.9.5. 解包实参列表

针对传参, 如果传入元组, 键值对不解包就当做一个参数对象来看待.

4.9.6. Lambda 表达式

与 C# 或 JS 一致, 仅仅语法形态不同:

lambda a,b: a + b
  • 使用 lambda 关键字引导.
  • 参数在冒号前.
  • 表达式在冒号后.

4.9.7. [略]文档字符串

基本略.

可以使用 函数名.__doc__ 输出文档注释

4.9.8. 函数注解

可以理解为类型描述, 该函数的参数与返回值标注类型. 记住一个示例即可:

image-20250916212201380

4.10. [略]小插曲: 编码风格

5. 数据结构

可以理解为复合数据类型的使用方法合集.

5.1. 列表详解

常用列表方法

方法含义
append(a, /)在列表末尾追加元素
extend(list, /)使用一个可迭代数据作为参数, 追加至列表尾部.
insert(i, a, /)在索引处插入元素.
remove(a, /)移除第一个出现的元素, 找不到触发 ValueError 异常.
pop([i], /)返回 i 位置处的元素, 无参数时返回列表末尾处的元素. 然后再列表中移除该元素.
clear()清空列表.
index(x[, start[, end]])返回元素 x 的索引, 后面的参数用于控制检索范围.
count(x)统计元素的次数.
sort(*, key=None, reverse=False)对原列表排序.
reverse()翻转列表.
copy()浅拷贝.

列表是可变的. 通常对可变数据进行操作, 如 insert, remove, sort 等, 方法不返回数据, 即返回 None.

5.1.1. 用列表实现堆栈

对标 JavaScript 中的用法模型, Python 也一样. 使用方法: append(), pop().

5.1.2. 用列表实现队列

考虑到队列在添加元素, 移除元素的过程中, 数据的索引会有所更新, 使用列表来实现队列效率很低.

应该使用 collections.deque 来实现队列, 涉及方法有: append() 和 popleft().

from collections import deque
queue = deque([...])
queue.append(...) # 入队
queue.popleft() # 出队

5.1.3. 列表推导式

重要模式.

基于列表元素生成新的列表, 不同于 JavaScript 中常用 map 方法, Python 多用列表推导式. 基本语法是:

[表达式 for 迭代变量 in 列表 if 过滤条件]

其中 if 过滤条件是可选的. 逻辑上相当于先 filter, 再 map.

迭代允许嵌套, 执行顺序是从左往右的 (这个是高级的). 例如将元素为列表的列表扁平化, 可以使用:

list = [[1,2],[3,4],[5,6]]
newlist = [item for sublist in list for item in sublist]

在 Python 中不推荐使用 for 循环来迭代生成数据, 因 for/if 无作用域, 会引入迭代变量, 从而污染作用域.

Python 也有 map 函数, 第一个参数是 lambda 表达式, 第二个参数是列表. 例如:

list = list(map(lambda x => x**2, range(10)))

生成笛卡尔积的示例:

[(x, y) for x in (1,2,3) for y in (1,2,3)]

5.1.4. 嵌套的列表推导式

列表推导式中的表达式可以是另一个推导式.

补充一个函数 zip(*iterables, strict=False)

  • 延迟执行, 该函数返回一个迭代器 (有点 Linq 的味道)
  • 依次迭代参数中的每一个迭代器, 并将其迭代的元素取出, 合并成一个新的元组作为迭代数据返回.
  • 默认并未严格要求参数中的每一个迭代器的长度, 默认遇到最短的迭代项结束, 参数 strict 可以指定是否会因为长度不同而报错.

如果迭代的是非等长的列表, 可以使用 itertools.zip_longest(*iterables, fillvalue=None) 来代替.

zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-

5.2. del 语句

del 可以基于切片语法在列表中删除片段, 以及删除整个变量.

语法: del 变量 或 del 列表[切片语法]

5.3. 元组和序列

内置的序列类型有: 列表, 元组, 以及 range. 本节主要介绍元组.

元组不可变, 列表可边, 其他几乎一样. 但是元组的成员可以是可变的.

创建语法比较特殊:

  • 空元组, 可以使用一个空的圆括号.
  • 单个元素的元组比较特殊, 一般使用一个元素加一个逗号, 圆括号可以省略.

元组 (列表) 支持解构语法, 例如: a, b, c = <tupleExpression>

5.4. 集合

逻辑上与数学中的集合概念相同: 无序, 不重复数据的集合. 并且包含集合运算方法.

创建使用花括号, 或者使用 set() 函数. 需要注意的是, 空集合只能使用 set() 函数, 因为 {} 是字典.

集合运算包括:

  • 元素的属于运算: in
  • 集合的交: &
  • 集合的并: |
  • 集合的差: -
  • 集合的对称差: ^

集合也支持推导式 (逻辑上可以看成特殊的列表).

5.5. 字典

映射类型, 与其他编程语言一致. 在 Python 中, 键是不可变类型, 如果元组仅包含数字或字符串 (基本值类型), 那么元组也可以作为键使用.

  • 创建字典的语法与 JSON 语法一致. 注意键必须使用引号括起来.
  • del 允许删除字典中的某个键值对.
  • 访问字典可以使用索引, 但是索引不存在时会异常, 可以使用 get() 方法, 键不存在时返回 None.
  • list(字典) 会得到键构成的列表, 顺序是插入的顺序.
  • 判断字典中是否存在某键, 可以使用 in 运算符, 例如: key in dic.

dict() 构造函数可以创建键值对. 也可以使用推导式来创建键值对.

dict([(k, v), (k, v), ...])
dict(k = v, k = v, ...)
[k: v for v in list]

5.6. 循环技巧

for 循环默认遍历列表, 元组, 字典, 以及集合时:

  • 列表/元组迭代的是项
  • 字典迭代的是键
  • 集合迭代的是元素.

如果要迭代键与内容:

  • 列表使用 enumerate() 可以迭代索引与项.
  • 字典使用 items() 方法可以迭代键与项. 字典遍历默认使用 keys() 方法, 也可以使用 values() 方法迭代值
  • 多个列表, 可以使用 zip() 来并行迭代, 生成新的结构.

一般来说, 如果迭代中会对元素进行修改, 不要直接对原始数据修改, 创建一个副本进行处理.

5.7. 深入条件控制

主要介绍逻辑表达式. if 和 while 等结构中不仅仅只有比较, 还有 and, or, not, in 等.

注意, Python 中链式关系运算符的判断, 与数学表达式一致. 即, 支持 3 > 2 > 1 形式的判断.

同时需要注意, 连续的 and 等运算与其他编程语言一样, 也存在逻辑中断的特性.

5.8. 序列与其他类型比较

同类型的序列 (列表, 元组, range 等) 可以进行大于, 小于, 等于的比较, 采用字典排序法的规则来进行比较.

6. 模块

保存代码的文件就被视为模块. 文件使用模块名和后缀名 (.py) 构成.

导入模块后, 使用 __name__ 可以获得模块名.

6.1. 模块详解

模块使用 import 来导入, 仅在在第一次遇到的时候执行.

导入模块的方式:

  • 导入模块, 并用模块名作为命名空间: import 模块名. 将模块名混入当前作用域, 将模块名当做命名空间使用.
  • 导入模块内的某个成员: from 模块名 import 成员名. 注意不会在当前作用域添加模块名.

模块内有自己的作用域, 不会出现污染问题.

导入的内容允许使用 as 重新命名.

允许使用 * 来导入所有, 但不建议这么使用.

6.1.1. 以脚本的形式执行模块

实际上就是在模块内部添加下面代码:

if __name__ == "__main__":
    # 以脚本形式运行才会执行该处代码
    pass

6.1.2. 模块搜索路径

导入模块时, 优先搜索内置模块, 然后利用 sys.path 来进行搜索模块.

  • 内置模块的路径由 sys.builtin_module_names 决定.
  • 模块的搜索路径由 sys.path 来决定 (脚本所在目录, 环境变量 PYTHONPATH, 以及依赖安装的默认值, 一般是 site-packages).

6.1.3. 已编译的 Python 文件

为了加载效率 (仅仅是加载模块), Python 会将模块编译后放在 __pycache__ 目录下. 并使用模块名, 版本号来命名.

6.2. 标准模块

就是内置模块. 略

6.3. dir() 函数

该函数可以查找模块定义的名称.

  • 直接使用 dir(模块名).
  • 无参数时列出的是当前定义的名称.
  • dir 不会列出内置名称.

6.4. 包 (raw)

包提供了一个模块集合, 相当于提供了子命名空间的逻辑. 通常是为了将一组紧密联系在一起的子模块组合在一起. 这类模块的访问允许在模块名中通过点来划分层级.

包的目录下必须含有一个 __ini__.py 文件, 来描述包内模块的组织. 有这个文件 Python 才会将该目录当做包来处理. 即使 __init__.py 是一个空文件. 实际上 __init__.py 里面是初始化的代码, 以及对 __all__ 的赋值.

然后介绍了导入的几种语法, 以及导入后如何引用的语法.

文档地址: https://docs.python.org/zh-cn/3.13/tutorial/modules.html#packages

6.4.1. 从包中导入 *

此时就需要为 __all__ 初始化了, 它表示 * 可以导入的名称. 该名称可以是子包名, 或成员名, 是一个字符串列表.

6.4.2. 相对导入

使用点表示当前目录, 两个点表示上级目录, 然后使用:

from . import xxx
from .. import xxx

6.4.3. 多目录中的包(略)

7. 输入与输出

主要介绍的是格式化字符串, 文件写入, 以及 JSON 格式的数据.

7.1. 更复杂的输出格式

  • 格式化字符串, 在字符串前使用 F 或 f.
  • 使用字符串的 format() 方法. 关于格式参考文档.
  • 利用字符串切片与合并来构造格式化字符串.

如果不需要格式, 可以使用字符串转换函数: str() 与 repr(). 第二个函数会包含代码细节的格式. 如引号, 转义字符串等.

文档中然后详细说明了上述内容的用法, 这里仅补充手动格式化的方法.

  • 字符串方法: rjust(), 在字符串左边添加指定个数的空格来实现对齐.
  • 对应的还有: ljest(), center(). 一个是右边添加空格, 一个是两端添加空格.
  • 字符串的 zfill() 方法, 在左边填充 0.

7.2 读写文件

使用 open 函数获得文件句柄.

f = open('fileName', 'mode', encoding='utf-8')
Last Updated:
Contributors: jk