文章目录


在这里插入图片描述


Ⅰ.Python的入门和安装

一、Python 的优势、缺点和应用场景

在正式学习语法之前,我们先简单了解一下 Python 这门语言的特点。知道它适合做什么、不适合做什么,后面学习时会更有方向。

1. Python 的优点

优点 说明
简单易学 Python 语法接近自然语言,代码可读性强,对编程初学者比较友好。
开发效率高 Python 拥有丰富的第三方库,很多功能不需要从零开始写,可以快速完成开发。
免费开源 Python 本身是开源的,学习资料和社区资源也非常丰富。
跨平台 Python 可以运行在 Windows、macOS、Linux 等多个操作系统上。
应用范围广 Python 可以用于 Web 开发、爬虫、数据分析、人工智能、自动化办公、自动化测试等方向。
可扩展性强 当某些功能对性能要求较高时,可以结合 C/C++ 等语言进行扩展。

2. Python 的缺点

缺点 解释
运行速度相对较慢 Python 通常属于解释型语言,代码在运行时逐行解释执行。相比 C、C++、Java 等语言,在一些高性能计算场景下速度会慢一些。
多线程性能受 GIL 影响 在 CPython 解释器中,由于 GIL(全局解释器锁)的存在,同一时刻通常只有一个线程在执行 Python 字节码。因此在 CPU 密集型任务中,多线程不一定能充分利用多核 CPU。
移动端和大型游戏开发不是主要优势 Python 可以做游戏开发,也可以参与一些移动端相关开发,但它并不是这些领域的主流首选语言。

3. Python 的常见应用场景

应用场景 说明
Web 开发 可以使用 Django、Flask、FastAPI 等框架开发网站和接口服务。
数据采集 也就是常说的爬虫,可以使用 requests、Scrapy、Selenium 等工具采集网页数据。
数据分析 可以使用 NumPy、Pandas、Matplotlib 等库进行数据处理和可视化。
人工智能 可以使用 PyTorch、TensorFlow、scikit-learn 等库进行机器学习和深度学习开发。
自动化办公 可以处理 Excel、Word、PDF、邮件、文件批量操作等重复性工作。
自动化测试 可以使用 pytest、unittest、Selenium、Appium 等工具进行接口或 UI 自动化测试。
运维脚本 可以编写脚本完成服务器管理、日志分析、定时任务等工作。

二、Python环境的安装

1.安装Python解释器

接下来该看一下Python的安装了。先安装python解释。

这里我进入windows下载地址,找到这里
在这里插入图片描述
选择想要下载的版本,一路向下安装即可。然后cmd打开窗口输入python3验证一下。出现如下就是成功。
在这里插入图片描述
2.安装PyCharm

安装完可以读python代码的解释器后,我们需要安装一个写代码的软件,这里我习惯使用pycharm,安装看这篇文章:https://www.runoob.com/w3cnote/pycharm-windows-install.html

3.写第一个程序

打开pycharm,在设置中把解释器设置为刚才安装的python路径,就可以创建一个python文件写下第一个程序了。
在这里插入图片描述

三、Python中的变量及其命名规则

  1. 定义:变量可以理解为:给数据起的名字。
  2. 形式变量名=值| 例如x=1(含义:将1的值赋给x),而在计算机里表示内存中开辟了一个值为1,而x指向这个1
  3. 变量的命名规则
    • a. 由字母、下划线和数字组成,且数字不能开头
    • b.长度任意长
    • c.区分大小写,不同大小写代表不同变量名
    • d.不能和Python关键字同名
  4. 查看python关键字的方法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']
  1. 命名建议:变量名建议做到见名知意,多个单词之间可以使用下划线连接。例如
user_name = "张三"
user_age = 18
student_score = 95

四、Python中的注释

注释是指代码中的语句不被执行的,我们通常是为了给代码做解释。Python的3种注释方式

# 我是单行注释

"""
我是多行
注释的写法1
"""

'''
我是多行
注释的写法2
'''

五、 一些简单常见的函数和认识ASCII表

函数 解释 使用举例
print() 控制台输出函数,通常用来临时看输出结果 print(123)
input() 控制台输入函数,用来在控制台往代码中传入值,传入的值为字符串 input(‘输入值:’)
type() 可以查询变量的类型,有列表,字典,字符串,元组,集合,布尔,数值类型等等 type(123)
id() 可以查询变量的物理内存地址,如果两个值物理内存地址相同,就认为是相等的 id(123)
chr() 将该整数转换为对应的ASCII中对应的字符 chr(65) 结果为A
ord() 将该字符转换为对应的ASCII中对应的十进制数字 ord(‘A’)结果为65

说到了chr和ord函数,简单介绍下

计算机底层只能识别二进制数据,也就是 01
但是我们平时使用的是字母、数字、符号等字符,所以就需要一套规则,把这些字符和数字对应起来。

下边是ASCII表
在这里插入图片描述

六、Python导入模块的方式

我们写python文件的结尾是.py。而模块可以简单理解为:一个 .py 文件就是一个模块
而导入模块通俗点说:有一些函数我们可以直接使用,但是有一些变量或者函数等如果在别的py文件里,我们这时候可以不写,通过导入对应的模块,然后就可以直接使用

下边是导入模块的一些写法

  • import 模块
  • import 模块 as 别名
  • import 模块1,模块2...
  • from 模块 import 功能1
  • from 模块 import *
  • from 模块 import 功能 as 别名

一般模块大致分为这几种类型:

  • Python 内置模块(Python 安装后自带的模块),例如random、time、os、sys
  • 三方模块(别人写好的模块),例如requests、pandas、flask
  • 自己写的模块

Ⅱ. Python的数据类型

一、Python 数据类型概览

分类 类型 示例 是否可变 是否有序 是否允许重复 常见用途
数字类型 intfloatboolcomplex 13.14True1 + 2j 不可变 - - 数字计算
字符串 str "hello" 不可变 有序 允许 文本处理
列表 list [1, 2, 3] 可变 有序 允许 存储一组可修改的数据
元组 tuple (1, 2, 3) 不可变 有序 允许 存储不希望被修改的数据
字典 dict {"name": "张三", "age": 18} 可变 有序 key 不允许重复 存储 key-value 数据
集合 set {1, 2, 3} 可变 无序 不允许 去重和集合运算
空值 NoneType None 不可变 - - 表示空值

查看数据类型可以使用 type()

name = "python"
print(type(name))  # <class 'str'>

判断某个变量是否属于指定类型,可以使用 isinstance()

name = "python"
print(isinstance(name, str))  # True

二、详解数据类型

①数字类型 Number

Python 中常见的数字类型有 intfloatboolcomplex

类型 说明 示例
int 整数 10
float 浮点数,也就是小数 3.14
bool 布尔值,只有 TrueFalse True
complex 复数 1 + 2j

1. 数字类型转换

print(int(3.14))      # 3    会去掉小数部分,不是四舍五入
print(float(10))      # 10.0
print(bool(0))        # False
print(bool(1))        # True  bool(非 0 数字)结果是True
print(complex(1, 2))  # (1+2j)

2. 数字运算

运算符 说明 示例
+ 加法 1 + 2
- 减法 3 - 1
* 乘法 2 * 3
/ 除法 5 / 2
// 整除 5 // 2
% 取余 5 % 2
** 幂运算 2 ** 3

示例:

a = 20
b = 10

print(a + b)
print(a - b)
print(a * b)
print(a / b)
print(a // b)
print(a % b)
print(a ** 2)

3. 进制表示和转换

常见进制有二进制、八进制、十进制和十六进制。

进制 表示方式 示例
二进制 0b 0b1010
八进制 0o 0o12
十进制 默认 10
十六进制 0x 0xa

常见转换函数:

num = 10

print(bin(num))  # 0b1010
print(oct(num))  # 0o12
print(hex(num))  # 0xa

也可以使用 int() 把其他进制字符串转成十进制:

print(int("1010", 2))  # 10
print(int("12", 8))    # 10
print(int("a", 16))    # 10

③字符串 String

字符串用来表示文本内容,在 Python 中属于不可变类型。

1. 字符串的创建

字符串可以使用单引号、双引号或三引号创建。

s1 = 'hello'
s2 = "hello"
s3 = """hello"""

三引号通常用来表示多行字符串:

text = """
第一行
第二行
第三行
"""
print(text)

2. 字符串索引和切片

字符串是有序的,可以通过索引获取指定位置的字符。

s = "python"

print(s[0])   # p
print(s[1])   # y
print(s[-1])  # n

索引从 0 开始,负数索引表示从右往左取。

字符串也支持切片(左闭右开):

s = "python"

print(s[0:2])   # py
print(s[:3])    # pyt
print(s[2:])    # thon
print(s[::-1])  # nohtyp

3. 字符串常见运算

s1 = "hello"
s2 = "python"

print(s1 + s2)      # 字符串拼接
print(s1 * 3)       # 字符串重复
print("h" in s1)    # 判断是否包含
print("x" not in s1)

4. 字符串常见方法

方法 说明
strip() 去除字符串两边的空白
split() 按指定字符分割字符串
replace() 替换字符串内容
find() 查找内容位置,找不到返回 -1
count() 统计出现次数
startswith() 判断是否以指定内容开头
endswith() 判断是否以指定内容结尾
upper() 转大写
lower() 转小写
join() 拼接字符串

示例:

s = " hello python "

print(s.strip())
print(s.replace("python", "world"))
print(s.find("python"))
print(s.upper())

split()join() 经常配合使用:

s = "a,b,c"
lst = s.split(",")
print(lst)  # ['a', 'b', 'c']

new_s = "-".join(lst)
print(new_s)  # a-b-c

5. 字符串格式化

字符串格式化常见方式有三种。

方式 写法 说明
百分号格式化 %s%d 旧写法,了解即可
format() "{}".format() 比百分号格式化更灵活
f-string f"{变量}" 推荐写法,简单直观

百分号格式化:

name = "张三"
age = 18

print("姓名:%s,年龄:%d" % (name, age))

format() 格式化:

name = "张三"
age = 18

print("姓名:{},年龄:{}".format(name, age))

format() 还支持位置参数、关键字参数和格式控制。

用法 示例 结果
默认顺序 "{} {}".format("hello", "python") hello python
位置参数 "{1} {0}".format("hello", "python") python hello
关键字参数 "{name} {age}".format(name="张三", age=18) 张三 18
保留小数 "{:.2f}".format(3.14159) 3.14
百分比 "{:.2%}".format(0.256) 25.60%
左对齐 "{:<6}".format("py") py
右对齐 "{:>6}".format("py") py
居中对齐 "{:^6}".format("py") py

示例:

print("{} {}".format("hello", "python"))           # hello python
print("{1} {0}".format("hello", "python"))         # python hello
print("{name} {age}".format(name="张三", age=18))  # 张三 18
print("{:.2f}".format(3.14159))                    # 3.14
print("{:.2%}".format(0.256))                      # 25.60%
print("{:^6}".format("py"))                        #      py 

f-string 格式化:

name = "张三"
age = 18
print(f"姓名:{name},年龄:{age}")

现在更推荐使用 f-string,写法简单,可读性也更好。

④列表 List

列表是 Python 中非常常用的数据类型,它是一个有序、可变的数据容器。

1. 列表的创建

nums = [1, 2, 3]
names = ["张三", "李四", "王五"]
empty_list = []

列表中可以保存不同类型的数据:

data = ["张三", 18, True, 3.14]

2. 列表索引和切片

列表和字符串一样,也支持索引和切片。

nums = [10, 20, 30, 40]

print(nums[0])    # 10
print(nums[-1])   # 40
print(nums[1:3])  # [20, 30]

3. 列表增加元素

方法 说明
append() 在列表末尾添加一个元素
extend() 把另一个可迭代对象中的元素追加到列表中
insert() 在指定位置插入元素

示例:

nums = [1, 2, 3]

nums.append(4)
print(nums)  # [1, 2, 3, 4]

nums.extend([5, 6])
print(nums)  # [1, 2, 3, 4, 5, 6]

nums.insert(0, 0)
print(nums)  # [0, 1, 2, 3, 4, 5, 6]

4. 列表删除元素

方法 说明
pop() 删除并返回指定位置的元素,默认删除最后一个
remove() 删除指定元素,只删除从左往右遇到的第一个
clear() 清空列表
del 删除指定位置的元素或整个变量

示例:

nums = [1, 2, 3, 4]

nums.pop()
print(nums)  # [1, 2, 3]

nums.remove(2)
print(nums)  # [1, 3]

del nums[0]
print(nums)  # [3]

nums.clear()
print(nums)  # []

5. 列表修改元素

nums = [1, 2, 3]

nums[0] = 100
print(nums)  # [100, 2, 3]

6. 列表查询和遍历

nums = [10, 20, 30]

print(len(nums))      # 获取长度
print(20 in nums)     # 判断元素是否存在
print(nums.index(20)) # 获取元素索引
print(nums.count(10)) # 统计元素出现次数

遍历列表:

nums = [10, 20, 30]

for num in nums:
    print(num)

7. 列表排序和反转

nums = [3, 1, 2]

nums.sort()
print(nums)  # [1, 2, 3]

nums.reverse()
print(nums)  # [3, 2, 1]

sort()sorted() 的区别:

nums = [3, 1, 2]

new_nums = sorted(nums)

print(nums)      # [3, 1, 2]
print(new_nums)  # [1, 2, 3]
方法 是否修改原列表 返回值
sort() 会修改原列表 None
sorted() 不修改原列表 返回一个新列表

8. 列表推导式

列表推导式可以快速生成列表。

nums = [i for i in range(5)]
print(nums)  # [0, 1, 2, 3, 4]

也可以加条件:

nums = [i for i in range(10) if i % 2 == 0]
print(nums)  # [0, 2, 4, 6, 8]

9. 列表嵌套

列表中也可以继续存放列表。

matrix = [
    [1, 2],
    [3, 4]
]

print(matrix[0][1])  # 2

10. 深拷贝和浅拷贝

列表是可变类型,在复制嵌套列表时需要注意深拷贝和浅拷贝。

浅拷贝只复制外层对象,内部嵌套的可变对象仍然是同一个。

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)

a[0].append(100)

print(a)
print(b)

深拷贝会递归复制内部对象。

import copy

a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)

a[0].append(100)

print(a)
print(b)
拷贝方式 说明
浅拷贝 只复制外层对象,内部嵌套的可变对象仍然共用
深拷贝 外层和内层都会复制,互相修改不影响

⑤元组 Tuple

元组和列表类似,也是有序的数据容器,但是元组创建后不能修改。

1. 元组的创建

创建方式 示例 说明
小括号创建 (1, 2, 3) 最常见写法
省略小括号 1, 2, 3 Python 也会识别为元组
单元素元组 (1,) 必须加逗号
t1 = (1, 2, 3)
t2 = 1, 2, 3

如果元组中只有一个元素,必须加逗号:

t = (1,)
print(type(t))  # <class 'tuple'>

如果不加逗号:

t = (1)
print(type(t))  # <class 'int'>

2. 元组索引、切片和遍历

t = (10, 20, 30, 40)

print(t[0])
print(t[-1])
print(t[1:3])

遍历元组:

t = (10, 20, 30)

for item in t:
    print(item)

3. 元组不可变

元组本身是不可变类型。

t = (1, 2, 3)

# t[0] = 100  # 报错

4. 元组中的可变元素

元组不能修改的是元素的引用,但如果元素本身是可变对象,那么这个对象内部的内容可以改变。

t = ([1, 2], 3)

t[0].append(100)
print(t)  # ([1, 2, 100], 3)

5. 元组和列表转换

t = (1, 2, 3)
lst = list(t)
print(lst)

lst = [1, 2, 3]
t = tuple(lst)
print(t)

6. 元组拼接和重复

t1 = (1, 2)
t2 = (3, 4)

print(t1 + t2)  # (1, 2, 3, 4)
print(t1 * 3)   # (1, 2, 1, 2, 1, 2)

⑥字典 Dict

字典使用 key-value 的形式保存数据,适合存储有明确含义的数据。

1. 字典的创建

user = {
    "name": "张三",
    "age": 18
}

字典中的每一项都由 keyvalue 组成。

{
    "key": "value"
}
注意点 说明
key 必须唯一 如果出现相同的 key,后面的值会覆盖前面的值
key 必须是不可变类型 例如数字、字符串、元组可以作为 key
字典保持插入顺序 Python 3.7 以后,字典会保持插入顺序

2. 字典增加和修改

user = {
    "name": "张三",
    "age": 18
}

user["city"] = "北京"
user["age"] = 20

print(user)

也可以使用 update()

user.update({"gender": "男"})
print(user)

3. 字典删除

方法 说明
pop() 删除指定 key,并返回对应 value
popitem() 删除最后一组 key-value
clear() 清空字典
del 删除指定 key 或整个变量

示例:

user = {
    "name": "张三",
    "age": 18,
    "city": "北京"
}

user.pop("city")
print(user)

del user["age"]
print(user)

user.clear()
print(user)

4. 字典查询

user = {
    "name": "张三",
    "age": 18
}

print(user["name"])

如果使用 [] 获取不存在的 key,会报错。

# print(user["city"])  # 报错

更安全的方式是使用 get()

print(user.get("city"))          # None
print(user.get("city", "未知"))  # 未知

5. 字典遍历

user = {
    "name": "张三",
    "age": 18
}

for key in user.keys():
    print(key)

for value in user.values():
    print(value)

for key, value in user.items():
    print(key, value)

6. 判断 key 是否存在

user = {
    "name": "张三",
    "age": 18
}

print("name" in user)  # True
print("city" in user)  # False

7. 字典推导式

d = {i: i * i for i in range(5)}
print(d)

输出:

{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

8. 字典嵌套

字典中也可以继续存放字典。

students = {
    "001": {"name": "张三", "age": 18},
    "002": {"name": "李四", "age": 19}
}

print(students["001"]["name"])

9. 字典注意事项

字典通过 key 查找数据速度很快,但相比列表会占用更多内存。但是字典的 key 必须是不可变类型,例如数字、字符串、元组等。列表、字典、集合不能作为字典的 key

d = {
    "name": "张三",
    1: "数字 key",
    (1, 2): "元组 key"
}

⑦集合 Set

集合是一个无序、不重复的数据容器,常用于去重和集合运算。

1. 集合的创建

s = {1, 2, 3}
print(type(s))  # <class 'set'>

创建空集合需要使用 set()

empty_set = set()

注意:

empty = {}
print(type(empty))  # <class 'dict'>

{} 创建的是空字典,不是空集合。

2. 集合的特点

特点 说明
无序 集合中的元素没有固定顺序
不重复 重复元素会自动去重
元素必须不可变 列表、字典、集合不能作为集合元素
s = {1, 2, 2, 3, 3}
print(s)  # {1, 2, 3}

3. 集合增加元素

方法 说明
add() 添加一个元素
update() 添加多个元素

示例:

s = {1, 2, 3}

s.add(4)
print(s)

s.update([5, 6, 7])
print(s)

4. 集合删除元素

方法 说明
remove() 删除指定元素,元素不存在会报错
discard() 删除指定元素,元素不存在不会报错
pop() 随机删除一个元素
clear() 清空集合

示例:

s = {1, 2, 3}

s.remove(1)
print(s)

s.discard(100)
print(s)

s.clear()
print(s)

5. 集合遍历和判断

s = {1, 2, 3}

for item in s:
    print(item)

print(1 in s)

6. 集合去重

集合最常见的作用之一就是去重。

nums = [1, 1, 2, 2, 3, 3]

new_nums = list(set(nums))
print(new_nums)

需要注意,集合是无序的,去重后不一定保留原来的顺序。

7. 集合运算

a = {1, 2, 3}
b = {3, 4, 5}

交集:

print(a & b)
print(a.intersection(b))

并集:

print(a | b)
print(a.union(b))

差集:

print(a - b)
print(a.difference(b))

对称差集:

print(a ^ b)
print(a.symmetric_difference(b))

⑧NoneType 空值

None 表示空值,常用于表示“没有结果”或“暂时没有数据”。

写法 说明
is None 推荐,用来判断是否为 None
== None 不太推荐
result = None
print(type(result))  # <class 'NoneType'>

判断一个值是否为 None,推荐使用 is None

result = None

if result is None:
    print("没有结果")

不太推荐这样写:

if result == None:
    print("没有结果")

三、常见类型转换总结

函数 说明
int() 转整数
float() 转小数
bool() 转布尔值
str() 转字符串
list() 转列表
tuple() 转元组
set() 转集合
dict() 转字典

示例:

print(int("123"))
print(float("3.14"))
print(str(123))
print(list("abc"))
print(tuple([1, 2, 3]))
print(set([1, 1, 2, 2]))

字典转换需要符合 key-value 结构:

data = [("name", "张三"), ("age", 18)]
print(dict(data))

四、常见数据的布尔值

在 Python 中,很多数据都可以转换为布尔值。

数据 bool 结果
0 False
0.0 False
"" False
[] False
{} False
set() False
None False
非 0 数字 True
非空字符串 True
非空列表 True
非空字典 True
非空集合 True

示例:

print(bool(0))
print(bool(""))
print(bool([]))
print(bool(None))
print(bool("hello"))
print(bool([1, 2, 3]))

五、可哈希和不可哈希

简单理解:可哈希的数据可以作为字典的 key,也可以放到集合中。

类型分类 常见类型
可哈希类型 intfloatboolstrtuple
不可哈希类型 listdictset

例如,字符串可以作为字典的 key:

user = {
    "name": "张三"
}

但是列表不能作为字典的 key:

# d = {[1, 2]: "hello"}  # 报错

集合中的元素也必须是可哈希的:

s = {1, "hello", (1, 2)}

下面这种写法会报错:

# s = {[1, 2], 3}  # 报错

六、数据类型中常见方法的时间复杂度

时间复杂度可以简单理解为:数据量变大时,代码执行次数增长得快不快。

常见写法:

复杂度 含义
O(1) 常数时间,数据量变大也基本不受影响
O(n) 线性时间,数据量越大,耗时越多
O(log n) 对数时间,常见于二分查找等场景
O(n log n) 常见于高效排序

1. 字符串常见操作复杂度

操作 示例 时间复杂度 说明
按索引取值 s[0] O(1) 直接根据位置取值
切片 s[1:5] O(k) k 为切片长度
拼接 s1 + s2 O(n + m) 需要生成新字符串
查找 find()in O(n) 最坏情况下需要遍历
替换 replace() O(n) 通常需要扫描整个字符串
分割 split() O(n) 通常需要扫描整个字符串
拼接序列 join() O(n) n 为最终字符串总长度

2. 列表常见操作复杂度

操作 示例 时间复杂度 说明
按索引取值 lst[i] O(1) 直接根据索引取值
按索引修改 lst[i] = x O(1) 直接修改指定位置
末尾添加 append() O(1) 平均为常数时间
指定位置插入 insert() O(n) 后面的元素需要移动
末尾删除 pop() O(1) 默认删除最后一个
指定位置删除 pop(i)del lst[i] O(n) 后面的元素需要移动
按值删除 remove() O(n) 需要先查找元素
查找元素 inindex() O(n) 最坏情况下需要遍历
排序 sort()sorted() O(n log n) Python 使用 Timsort

3. 字典常见操作复杂度

操作 示例 时间复杂度 说明
根据 key 取值 d[key] O(1) 平均情况
新增或修改 d[key] = value O(1) 平均情况
删除 key pop()del O(1) 平均情况
判断 key 是否存在 key in d O(1) 平均情况
遍历字典 for key in d O(n) 需要遍历全部元素
获取所有 key/value/item keys()values()items() O(1) 返回视图对象,真正遍历仍是 O(n)

4. 集合常见操作复杂度

操作 示例 时间复杂度 说明
添加元素 add() O(1) 平均情况
删除元素 remove()discard() O(1) 平均情况
判断元素是否存在 x in s O(1) 平均情况
遍历集合 for x in s O(n) 需要遍历全部元素
交集 a & b O(min(len(a), len(b))) 通常遍历较小集合
并集 `a b` O(len(a) + len(b))
差集 a - b O(len(a)) 遍历左侧集合

Ⅲ.条件语句、循环语句和逻辑运算符

一、条件语句 if

条件语句用于根据条件是否成立,决定执行哪一段代码。

if 语句常见结构:

结构 适合场景 语法示例
if 只判断一种情况 if 条件:
if...else 二选一 if 条件: ... else: ...
if...elif 多个条件判断 if 条件1: ... elif 条件2: ...
if...elif...else 多个条件 + 兜底情况 if 条件1: ... elif 条件2: ... else: ...

示例:

score = 85

if score >= 90:
    print("优秀")
elif score >= 80:
    print("良好")
elif score >= 60:
    print("及格")
else:
    print("不及格")

执行逻辑:

关键字 作用 是否需要条件 是否可以出现多次
if 开始条件判断 需要 通常只出现一次
elif 继续判断其他条件 需要 可以出现多次
else 当前面条件都不成立时执行 不需要 最多出现一次

需要注意:

注意点 说明
冒号 ifelifelse 后面都要加 :
缩进 Python 使用缩进表示代码块
执行顺序 从上到下判断,命中一个分支后,后面的分支不再执行

二、三元表达式

三元表达式可以把简单的 if...else 写成一行。

语法格式:

变量 = 条件成立时的值 if 条件 else 条件不成立时的值

举例:

age = 18

if age >= 18:
    result = "成年"
else:
    result = "未成年"

改为三元表达式:

age = 18
result = "成年" if age >= 18 else "未成年"

使用建议:

场景 是否推荐
简单二选一赋值 推荐
判断逻辑较复杂 不推荐
多个条件嵌套 不推荐,建议使用普通 if...elif...else

三、range() 函数

range() 常和 for 循环一起使用,用来生成一个整数序列。

写法 含义 示例结果
range(stop) 0stop - 1 range(5) 得到 0,1,2,3,4
range(start, stop) startstop - 1 range(1,5) 得到 1,2,3,4
range(start, stop, step) 按指定步长生成 range(1,10,2) 得到 1,3,5,7,9

示例:

for i in range(1, 6):
    print(i)

倒序:

for i in range(5, 0, -1):
    print(i)

特点:

特点 说明
左闭右开 包含开始值,不包含结束值
默认开始值 不写开始值时,默认从 0 开始
默认步长 不写步长时,默认是 1
支持倒序 步长可以是负数

四、while 和 for 循环

1. while 循环

while 循环会在条件成立时,不断重复执行代码。

i = 1

while i <= 5:
    print(i)
    i += 1

适合场景:

场景 示例
不确定循环次数 用户一直输入,直到输入 q 结束
根据条件控制循环 当库存大于 0 时继续售卖
需要手动修改循环变量 i += 1

2. for 循环

for 循环通常用来遍历可迭代对象。

names = ["张三", "李四", "王五"]

for name in names:
    print(name)

常见遍历对象:

对象 示例
字符串 for ch in "python"
列表 for item in [1, 2, 3]
元组 for item in (1, 2, 3)
字典 for key in dict_obj
集合 for item in set_obj
range() for i in range(5)

五、break、continue 和循环 else

循环中常用的控制关键字有 breakcontinueelse

语句 作用
break 立刻结束离它最近的一层循环
continue 跳过本次循环,直接进入下一次循环
else 循环正常结束后执行,被 break 结束则不执行

1. break 示例

for i in range(1, 6):
    if i == 3:
        break
    print(i)

输出:

1
2

2. continue 示例

for i in range(1, 6):
    if i == 3:
        continue
    print(i)

输出:

1
2
4
5

3. 循环 else 示例

for i in range(1, 4):
    print(i)
else:
    print("循环正常结束")

如果循环被 break 终止,else 不会执行。

for i in range(1, 6):
    if i == 3:
        break
    print(i)
else:
    print("循环正常结束")

while...elsefor...else 的规则一致:

情况 是否执行 else
循环自然结束 执行
循环被 break 终止 不执行

4. 输出 2 到 100 的质数

质数指只能被 1 和它本身整除的数。

prime_list = []

for num in range(2, 101):
    for i in range(2, num):
        if num % i == 0:
            break
    else:
        prime_list.append(num)

print(prime_list)

这里内层 for...else 的含义:

情况 说明
出现 break 说明 num 能被某个数整除,不是质数
没有出现 break 说明循环正常结束,num 是质数

六、逻辑运算符

逻辑运算符用于连接多个条件判断。

运算符 含义 示例
and 并且 条件1 and 条件2
or 或者 条件1 or 条件2
not 取反 not 条件

1. Python 中哪些值为假

bool 结果
False False
0 False
0.0 False
"" False
[] False
{} False
set() False
None False

其他大多数非空、非零数据会被判断为 True

2. and、or、not 对比

表达式 说明 示例结果
True and True 两边都为真才为真 True
True and False 有一个为假就为假 False
True or False 有一个为真就为真 True
False or False 两边都为假才为假 False
not True 对结果取反 False
not False 对结果取反 True

示例:

age = 20
money = 100

if age >= 18 and money >= 50:
    print("可以进入")
is_vip = True
has_coupon = False

if is_vip or has_coupon:
    print("可以享受优惠")
is_login = False

if not is_login:
    print("请先登录")

3. and 和 or 的返回值

andor 不一定直接返回 TrueFalse,它们会返回参与运算的某个值。

运算符 规则
and 第一个值为假,返回第一个值;第一个值为真,返回第二个值
or 第一个值为真,返回第一个值;第一个值为假,返回第二个值

示例:

print(0 and 100)       # 0
print(1 and 100)       # 100
print(1 or 100)        # 1
print(0 or 100)        # 100
print("" or "默认值")  # 默认值

4.逻辑运算符的优先级

优先级 运算符
not
and
or

示例:

print(True or False and False)

先计算 and,再计算 or,结果是:

True

如果想改变优先级,可以使用小括号:

print((True or False) and False)  # False

七、运算符优先级

Python 常见运算符优先级如下,优先级从高到低排列。

优先级 运算符 说明
1 () 小括号,优先计算
2 ** 幂运算
3 +x-x~x 正号、负号、按位取反
4 *///% 乘、除、整除、取余
5 +- 加、减
6 <<>> 位移运算
7 & 按位与
8 ^ 按位异或
9 ` `
10 innot inisis not<<=>>=!=== 比较、成员、身份运算
11 not 逻辑非
12 and 逻辑与
13 or 逻辑或
14 if...else 三元表达式
15 lambda 匿名函数

建议:复杂表达式尽量加小括号,不要完全依赖记忆优先级。


Ⅳ.函数、参数、作用域和匿名函数

一、函数的定义和调用

1. 基本语法

使用 def 定义函数:

def 函数名(参数):
    """函数说明"""
    函数体
    return 返回值

示例:

def greet(name):
    """根据姓名输出问候语"""
    return f"你好,{name}"

result = greet("张三")
print(result)

函数必须先定义,后调用。

2. 函数的特点

特点 说明
代码复用 相同功能只需要编写一次
封装功能 将一段逻辑封装成独立代码块
可以接收参数 调用时向函数传入数据
可以返回结果 使用 return 返回处理结果
先定义后调用 函数定义完成后才能调用

3. 函数命名规范

规则 示例
由字母、数字和下划线组成 get_user1()
不能以数字开头 1get_user() 错误
不能使用 Python 关键字 def() 错误
推荐使用小写字母和下划线 get_user_info()
名称应体现函数用途 calculate_total()

二、函数参数

参数用于向函数传递数据。

def add(a, b):
    return a + b

print(add(10, 20))

1. 形参和实参

名称 说明 示例
形参 定义函数时写在括号中的参数 def add(a, b) 中的 ab
实参 调用函数时实际传入的值 add(10, 20) 中的 1020

2. 常见参数类型

参数类型 写法 说明
位置参数 func(1, 2) 按照参数位置传值
关键字参数 func(a=1, b=2) 根据参数名传值
默认参数 def func(a=1) 未传值时使用默认值
可变位置参数 def func(*args) 接收任意数量的位置参数
可变关键字参数 def func(**kwargs) 接收任意数量的关键字参数

① 位置参数和关键字参数

def user_info(name, age):
    print(f"姓名:{name},年龄:{age}")

user_info("张三", 18)
user_info(age=18, name="张三")

位置参数必须写在关键字参数之前:

user_info("张三", age=18)

② 默认参数

def greet(name, message="你好"):
    print(f"{message}{name}")

greet("张三")
greet("李四", "欢迎")

默认参数应放在非默认参数后面:

def greet(name, message="你好"):
    pass

不要使用列表、字典等可变对象作为默认值。可以使用 None 代替:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

③ 可变位置参数 *args

*args 会把多余的位置参数接收为元组。

def total(*args):
    return sum(args)

print(total(1, 2, 3, 4))  # 10

④ 可变关键字参数 **kwargs

**kwargs 会把多余的关键字参数接收为字典。

def user_info(**kwargs):
    print(kwargs)

user_info(name="张三", age=18)

输出:

{'name': '张三', 'age': 18}

3. 参数顺序

定义函数时,常见参数顺序如下:

def func(普通参数, 默认参数=, *args, 仅限关键字参数, **kwargs):
    pass

示例:

def example(a, b=10, *args, c=20, **kwargs):
    print(a, b, args, c, kwargs)

example(1, 2, 3, 4, c=5, name="张三")

三、参数解包

*** 除了接收可变参数,也可以在调用函数时解包数据。

符号 解包对象 作用
* 列表、元组等序列 解包为多个位置参数
** 字典 解包为多个关键字参数

1. 序列解包

def add(a, b):
    return a + b

nums = [10, 20]
print(add(*nums))

等价于:

print(add(10, 20))

2. 字典解包

def user_info(name, age):
    print(name, age)

user = {"name": "张三", "age": 18}
user_info(**user)

3. 接收多个值

first, *middle, last = [1, 2, 3, 4, 5]

print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

四、函数返回值 return

return 用于结束函数,并把结果返回给调用方。

def add(a, b):
    return a + b

result = add(10, 20)
print(result)

1. return 的特点

写法 返回结果 说明
不写 return None 函数执行完自动返回 None
return None 立即结束函数
return value value 结束函数并返回一个值
return a, b 元组 (a, b) 一次返回多个值

2. return 会结束函数

def test():
    print("开始")
    return
    print("结束")  # 不会执行

test()

3. 返回多个值

def calculate(a, b):
    return a + b, a - b

result = calculate(10, 5)
print(result)  # (15, 5)

add_result, sub_result = calculate(10, 5)
print(add_result, sub_result)

五、命名空间和作用域

1. 什么是命名空间

命名空间可以简单理解为:保存变量名与对象对应关系的空间。

命名空间 说明
内置命名空间 保存 print()len() 等内置名称
全局命名空间 保存模块中定义的变量和函数
局部命名空间 保存函数内部定义的变量

2. 全局变量和局部变量

类型 定义位置 可使用范围
全局变量 函数外部 当前模块中
局部变量 函数内部 当前函数中
name = "全局变量"

def test():
    age = 18
    print(name)
    print(age)

test()

函数内部可以读取全局变量,但直接赋值时默认创建同名局部变量。

count = 10

def test():
    count = 20
    print(count)

test()
print(count)

输出:

20
10

3. global 和 nonlocal

关键字 作用
global 在函数内部修改全局变量
nonlocal 在嵌套函数中修改外层函数变量

使用 global

count = 10

def test():
    global count
    count = 20

test()
print(count)  # 20

使用 nonlocal

def outer():
    count = 10

    def inner():
        nonlocal count
        count = 20

    inner()
    print(count)

outer()  # 20

4. LEGB 查找规则

Python 查找变量时遵循 LEGB 顺序:

顺序 名称 说明
L Local 当前函数的局部作用域
E Enclosing 外层嵌套函数作用域
G Global 当前模块的全局作用域
B Built-in Python 内置作用域
name = "Global"

def outer():
    name = "Enclosing"

    def inner():
        name = "Local"
        print(name)

    inner()

outer()  # Local

六、递归函数

函数在内部调用自己,称为递归函数。

递归通常包含两部分:

部分 作用
终止条件 防止函数无限调用自己
递归调用 将问题逐步缩小

计算阶乘:

def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 120

执行过程:

factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 120

需要注意:

注意点 说明
必须有终止条件 否则会无限递归并报错
层数不能太深 每次调用都会占用栈空间
简单问题优先使用循环 循环通常更节省内存

七、常见内置函数

内置函数可以直接使用,不需要导入模块。

1. 类型和数据处理

函数 作用 示例
type() 查看对象类型 type(1)
isinstance() 判断对象类型 isinstance(1, int)
int() 转换为整数 int("10")
float() 转换为浮点数 float("3.14")
str() 转换为字符串 str(100)
list() 转换为列表 list("abc")
tuple() 转换为元组 tuple([1, 2])
set() 转换为集合 set([1, 1, 2])
dict() 转换为字典 dict(name="张三")

2. 序列和计算

函数 作用 示例
len() 获取长度 len([1, 2, 3])
max() 获取最大值 max([1, 2, 3])
min() 获取最小值 min([1, 2, 3])
sum() 求和 sum([1, 2, 3])
sorted() 排序并返回新列表 sorted([3, 1, 2])
reversed() 反向迭代 list(reversed([1, 2]))
range() 生成整数序列 range(5)

3. 遍历辅助函数

函数 作用 示例
enumerate() 同时获取索引和值 enumerate(["a", "b"])
zip() 将多个序列按位置组合 zip([1, 2], ["a", "b"])
map() 对每个元素执行函数 map(str, [1, 2])
filter() 根据条件过滤元素 filter(func, data)
all() 所有元素为真时返回 True all([True, True])
any() 任意元素为真时返回 True any([False, True])

enumerate() 示例:

names = ["张三", "李四"]

for index, name in enumerate(names):
    print(index, name)

zip() 示例:

names = ["张三", "李四"]
ages = [18, 20]

for name, age in zip(names, ages):
    print(name, age)

八、匿名函数 lambda

lambda 用于创建简单的匿名函数。

普通函数:

def add(a, b):
    return a + b

匿名函数:

add = lambda a, b: a + b
print(add(10, 20))

语法格式:

lambda 参数: 返回值表达式

1. lambda 的特点

特点 说明
没有正式函数名 通常临时使用或赋值给变量
只能写一个表达式 不能写多条普通语句
自动返回结果 不需要写 return
适合简单逻辑 复杂逻辑应使用 def

2. 常见使用场景

排序时指定规则:

users = [
    {"name": "张三", "age": 20},
    {"name": "李四", "age": 18}
]

users.sort(key=lambda user: user["age"])
print(users)

搭配 map()

nums = [1, 2, 3]
result = list(map(lambda x: x * x, nums))
print(result)  # [1, 4, 9]

搭配 filter()

nums = [1, 2, 3, 4, 5]
result = list(filter(lambda x: x % 2 == 0, nums))
print(result)  # [2, 4]

Ⅴ. 面向对象编程

一、类、对象和实例化

1.认识类

举一个简单的例子:把人看成一个类,人有很多共同的属性(年龄,姓名,身高等待),人有很多共同的方法(去吃饭,去睡觉,去看蔡徐坤跳舞等等)。而李明就是人的一个对象,他有人(类)的属性和方法。

在Python中,类通过 class 关键字定义,类名通用习惯为首字母大写,Python3中类基本都会继承于object类,语法格式如下,我们创建一个People类:

# -----------------------方式1
# # 创建People类,People为类名,继承object类也可以不继承object类;两者区别不大,但没有继承于object类使用多继承时可能会出现问题。
class People(object):  
    pass

# -----------------------方式2
# 默认继承object类
class People:
    pass

有了People类的定义,就可以创建出具体的实例.如下我们创建两个People类的实例,并且使用类中的方法和公共属性

class People:
    def __init__(self):
        self.China = "中国"

    def chifan(self):
        print('我要吃饭')

    def wc(self):
        print('我要上厕所')


if __name__ == '__main__':
    p1 = People()
    p2 = People()

    p1.chifan()  # 我要吃饭
    p1.wc()  # 我要上厕所

    print(p2.China)  # 中国
    p2.chifan()  # 我要吃饭

我们看到刚刚上述,每个方法里有一个self(名字可改,默认self),该值是类中的方法需要的,哪个对象调用方法或者属性,self就是那个值self 表示当前调用方法的对象。调用实例方法时,不需要手动传入 self,Python 会自动完成。类名通常采用大驼峰命名法。

class People:
    def __init__(self):
        self.China = "中国"

    def chifan(self):
        print('我要吃饭')

    def wc(self):
        print('我要上厕所')

    def aaa(self):
        print(self.China)
        self.chifan()
        self.wc()


if __name__ == '__main__':
    p = People()
    p.aaa()

在这里插入图片描述

2. 实例属性和类属性

属性 定义位置 归属 调用方式
实例属性 通常定义在 __init__() 每个对象独立拥有 对象.属性
类属性 定义在类中、方法外 所有对象共同使用 类.属性
class Student:
    school = "第一中学"

    def __init__(self, name):
        self.name = name


s1 = Student("张三")
s2 = Student("李四")

print(s1.name)
print(Student.school)
print(s2.school)

修改类属性时,推荐通过类名修改:

Student.school = "第二中学"

3. property 属性

property 可以让方法像属性一样访问,常用于控制属性的读取和修改。

class Student:
    @property
    def age(self):
        return 15

student = Student()
print(student.age)

二、实例方法、静态方法和类方法

python中有三种方法,分别是实例方法、静态方法(staticmethod) 和 类方法(classmethod)。

  • 实例方法只能通过实例对象调用,不能通过类进行调用。实例方法再定义时候使用关键字self,self代表实例对象本身。
  • 静态方法可以使用实例对象调用,也可以使用类进行调用,他的的特点没有参数限制,定义时需要在函数前加@staticmethod
  • 类方法可以被类调用,也可以被实例对象调用,实例调用可以给类增加属性,类的属性修改需要通过类进行修改,类方法需要使用关键字cls,定义时候需要在函数前加@classmethod

看一个示例

class A:
    def fun1(self):
        print('我是实例方法(对象方法),第一个参数是该对象')

    @classmethod
    def fun2(cls):
        print('我是类方法,第一个参数是类')

    @staticmethod
    def fun3(a, b):
        print('我是静态方法,我的参数和此类毫无关系,不写参数也可以')


if __name__ == '__main__':
    a = A()
    a.fun1()  # 我是实例方法(静态方法),第一个参数是该对象
    A.fun1(a)  # 可以用类调用对象方法吗?答案是否定的(虽然此地方可以运行,但是这句话还算对)

    A.fun2()  # 我是类方法,第一个参数是类
    A.fun3(2, 3)  # 我是静态方法,我的参数和此类毫无关系,不写参数也可以
    a.fun2()  # 我是类方法,第一个参数是类。(虽然传递的是对象,但是会知道该对象属于哪个类)
    a.fun3(1, 2)  # 我是静态方法,我的参数和此类毫无关系,不写参数也可以

在这里插入图片描述

三种方法除了使用上,其他地方有什么不同呢?

  • 静态方法类似普通函数,参数里面不用传递 self。有一些方法和类相关,但是又不需要类中的任何信息,出于对代码的理解和维护,就可用使用静态方法。静态方法最大的优点是能节省开销,因为它不会绑定到实例对象上,它在内存中只生成一个。
  • 而实例方法每个实例对象都是独立的,开销较大。
  • 而类方法与实例方法类似,但是类方法传递的不是实例,而是类本身。当需要和类交互而不需要和实例交互时,就可以选择类方法。

三、面向对象三大特性(封装、继承和多态)

特性 说明 主要作用
封装 将数据和操作数据的方法放在类中 隐藏实现细节、保护数据
继承 子类复用父类的属性和方法 减少重复代码
多态 不同对象调用同一方法时表现不同 提高程序扩展性

1.封装

封装不仅是把代码放进类中,还包括限制外部直接操作内部数据。

写法 含义
name 普通属性,可以直接访问
_name 约定为内部属性,外部不应直接使用
__name 私有属性,会触发名称改写
class User:
    def __init__(self, name, password):
        self.name = name
        self.__password = password

    def check_password(self, password):
        return self.__password == password


user = User("张三", "123456")
print(user.check_password("123456"))

Python 的私有属性不是绝对无法访问,而是通过名称改写降低外部误用的可能。

2.继承和 super()

①继承和方法重写

子类可以继承父类的属性和方法,也可以重新定义父类已有的方法。

class Animal:
    def eat(self):
        print("正在吃东西")

    def speak(self):
        print("动物发出声音")


class Dog(Animal):
    def speak(self):
        print("汪汪")


dog = Dog()
dog.eat()
dog.speak()

②super() 调用父类

super()常用于在子类中调用父类的方法。

class Person:
    def __init__(self, name):
        self.name = name


class Student(Person):
    def __init__(self, name, score):
        super().__init__(name)
        self.score = score

③多继承和 MRO

Python 支持一个子类继承多个父类:

class A:
    pass


class B:
    pass


class C(A, B):
    pass

当多个父类存在同名方法时,Python 会按照 MRO 顺序查找。

方法 作用
类.mro() 查看完整的方法查找顺序
类.__mro__ 查看 MRO 元组
super() 按照 MRO 调用下一个类的方法
print(C.mro())

实际开发中应谨慎使用复杂多继承,避免调用关系难以理解。

3.多态

多态表示不同类型的对象,可以通过相同的方法调用产生不同的行为。

class Dog:
    def speak(self):
        print("汪汪")


class Cat:
    def speak(self):
        print("喵喵")


def make_sound(animal):
    animal.speak()


make_sound(Dog())
make_sound(Cat())

make_sound() 不关心对象的具体类型,只要求对象能够调用 speak() 方法。这也体现了 Python 的鸭子类型。

四、反射方法

反射是通过字符串操作对象属性或方法的能力。

方法 作用
hasattr(obj, name) 判断对象是否有指定属性
getattr(obj, name) 获取指定属性
setattr(obj, name, value) 设置指定属性
delattr(obj, name) 删除指定属性
class User:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print(f"你好,我是{self.name}")


user = User("张三")

print(hasattr(user, "name"))
print(getattr(user, "name"))

setattr(user, "age", 18)
print(user.age)

method = getattr(user, "say_hello")
method()

getattr() 提供默认值,可以避免属性不存在时报错:

value = getattr(user, "city", "未知")

五、常见魔术方法

魔术方法以双下划线开头和结尾,Python 会在特定场景下自动调用。

1. 常见方法

方法 触发时机 作用
__new__() 创建对象时 创建并返回实例
__init__() 对象创建后 初始化实例属性
__str__() print(obj)str(obj) 返回面向用户的字符串
__repr__() repr(obj)、交互环境 返回更明确的对象描述
__len__() len(obj) 返回对象长度
__call__() obj() 让对象可以像函数一样调用
__enter__() 进入 with 返回上下文对象
__exit__() 离开 with 执行资源清理
class User:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f"用户:{self.name}"

    def __repr__(self):
        return f"User(name={self.name!r})"

2. 运算符和比较

方法 对应操作
__add__() +
__sub__() -
__mul__() *
__eq__() ==
__lt__() <
__le__() <=
class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Number(self.value + other.value)

    def __str__(self):
        return str(self.value)


n1 = Number(10)
n2 = Number(20)
print(n1 + n2)

3. 属性访问

方法 触发时机
__getattr__() 正常查找属性失败后
__getattribute__() 每次访问属性时
__setattr__() 设置属性时
__delattr__() 删除属性时

这些方法会影响所有属性访问,使用不当容易引起无限递归,初学阶段了解即可。

六、抽象类

抽象类用于规定子类必须实现哪些方法。

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    def speak(self):
        print("汪汪")


dog = Dog()
dog.speak()
内容 说明
ABC 抽象类基类
@abstractmethod 声明抽象方法
抽象类 主要用于定义规范和公共行为
子类 必须实现全部抽象方法才能实例化

Ⅵ. 推导式、迭代器、生成器、闭包和装饰器

一、序列推导式

推导式可以使用简洁的语法快速创建列表、字典或集合。

1. 列表推导式

基本格式:

[表达式 for 变量 in 可迭代对象]

示例:

nums = [i * i for i in range(1, 6)]
print(nums)  # [1, 4, 9, 16, 25]

添加判断条件:

even_nums = [i for i in range(10) if i % 2 == 0]
print(even_nums)  # [0, 2, 4, 6, 8]

使用三元表达式:

result = ["偶数" if i % 2 == 0 else "奇数" for i in range(5)]
print(result)

2. 字典和集合推导式

类型 语法格式 示例
列表推导式 [表达式 for 变量 in 对象] [i * 2 for i in range(5)]
字典推导式 {key: value for 变量 in 对象} {i: i * i for i in range(5)}
集合推导式 {表达式 for 变量 in 对象} {i % 3 for i in range(10)}
square_dict = {i: i * i for i in range(5)}
print(square_dict)

remainder_set = {i % 3 for i in range(10)}
print(remainder_set)

元组没有元组推导式。下面的写法会创建生成器:

generator = (i * i for i in range(5))

3. 使用建议

场景 建议
简单转换或筛选 使用推导式,代码更简洁
存在多层循环 根据可读性决定是否使用
判断逻辑复杂 使用普通 for 循环
数据量很大 考虑使用生成器表达式

二、可迭代对象和迭代器

1. 基本概念

概念 说明
可迭代对象 可以被 for 循环遍历的对象
迭代器 可以逐个返回数据,并记录当前迭代位置的对象
迭代 按顺序逐个获取数据的过程

常见可迭代对象:

类型 是否可迭代
字符串
列表
元组
字典
集合
range
生成器 是,同时也是迭代器

2. iter() 和 next()

iter() 可以从可迭代对象中获得迭代器,next() 用于获取下一个元素。

nums = [10, 20, 30]
iterator = iter(nums)

print(next(iterator))  # 10
print(next(iterator))  # 20
print(next(iterator))  # 30

元素全部取完后,再调用 next() 会抛出 StopIteration 异常。

也可以提供默认值:

print(next(iterator, "没有数据"))

3. for 循环的迭代过程

下面的代码:

for num in [10, 20, 30]:
    print(num)

可以简单理解为:

iterator = iter([10, 20, 30])

while True:
    try:
        num = next(iterator)
        print(num)
    except StopIteration:
        break

4. 自定义迭代器

迭代器需要实现 __iter__()__next__()

class Counter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.end:
            raise StopIteration

        value = self.current
        self.current += 1
        return value


for num in Counter(1, 5):
    print(num)
方法 作用
__iter__() 返回迭代器对象本身,这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
__next__() (Python 2 里是 next())会返回下一个迭代器对象。
StopIteration StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

三、生成器

生成器是一种特殊的迭代器,可以按需生成数据,不需要一次性把全部数据存入内存。

1. 创建生成器

生成器有两种常见创建方式:

方式 示例
生成器表达式 (i * i for i in range(5))
生成器函数 函数中使用 yield

生成器表达式:

generator = (i * i for i in range(5))

print(next(generator))  # 0
print(next(generator))  # 1

生成器函数:

def count_up_to(end):
    num = 1

    while num <= end:
        yield num
        num += 1


for num in count_up_to(5):
    print(num)

2. yield 的作用

return yield
返回结果并结束函数 返回一个值并暂停函数
再次调用会重新执行 再次迭代会从暂停处继续
普通函数使用 生成器函数使用

函数中只要出现 yield,调用函数时就不会立刻执行函数体,而是返回生成器对象。

def demo():
    print("开始")
    yield 1
    print("继续")
    yield 2


generator = demo()
print(next(generator))
print(next(generator))

3. 生成器的优势

优势 说明
节省内存 数据使用时才生成
支持大量数据 不需要一次性加载全部内容
保留执行状态 每次从上次暂停的位置继续
可以配合 for 生成器本身就是迭代器

生成器适合读取大文件、处理大量数据和构建数据处理流水线。

4. send() 传值

send() 可以在恢复生成器执行时向内部传值。

def receiver():
    value = yield "开始"
    yield f"收到:{value}"


generator = receiver()

print(next(generator))
print(generator.send("hello"))

生成器第一次启动时,需要使用 next()send(None)

四、闭包

闭包是一个能够记住外层函数变量的内部函数。

def outer(x):
    def inner(y):
        return x + y

    return inner


add_10 = outer(10)
print(add_10(5))  # 15

即使 outer() 已经执行结束,inner() 仍然可以使用变量 x

1. 闭包的条件

条件 说明
存在嵌套函数 一个函数定义在另一个函数内部
内部函数使用外层变量 使用的不是内部函数自己的局部变量
外层函数返回内部函数 返回函数对象,而不是调用结果

2. nonlocal

如果内部函数需要修改外层函数变量,需要使用 nonlocal

def counter():
    count = 0

    def add():
        nonlocal count
        count += 1
        return count

    return add


add_count = counter()

print(add_count())  # 1
print(add_count())  # 2

3. 闭包的用途

用途 说明
保存状态 多次调用之间保留数据
函数工厂 根据参数生成不同功能的函数
隐藏数据 外部不能直接访问内部变量
实现装饰器 装饰器通常依赖闭包保存原函数

需要注意:闭包长期引用外部变量时,这些变量不会被垃圾回收。

五、装饰器

装饰器可以在不修改原函数代码和调用方式的情况下,为函数增加功能。

1. 装饰器基本结构

def decorator(func):
    def wrapper(*args, **kwargs):
        print("函数执行前")
        result = func(*args, **kwargs)
        print("函数执行后")
        return result

    return wrapper

使用装饰器:

@decorator
def add(a, b):
    return a + b


print(add(10, 20))

下面两种写法等价:

@decorator
def add(a, b):
    return a + b
def add(a, b):
    return a + b


add = decorator(add)

**2. 为什么使用 *args 和 kwargs

包装函数通常无法提前确定原函数接收哪些参数,因此使用可变参数统一接收和传递。

def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

3. 保留原函数信息

装饰后,函数名和文档字符串默认会变成包装函数的信息。可以使用 functools.wraps 保留原函数信息。

from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

实际编写装饰器时,建议始终使用 @wraps(func)

4. 带参数的装饰器

带参数的装饰器需要再增加一层函数。

from functools import wraps


def repeat(count):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None

            for _ in range(count):
                result = func(*args, **kwargs)

            return result

        return wrapper

    return decorator

使用:

@repeat(3)
def say_hello():
    print("hello")


say_hello()

5. 多个装饰器

多个装饰器会从下往上进行装饰,调用时从上往下进入。

@decorator_a
@decorator_b
def func():
    pass

等价于:

func = decorator_a(decorator_b(func))

6. 常见应用场景

场景 作用
日志记录 记录函数调用信息
权限验证 调用函数前检查权限
运行计时 统计函数执行时间
缓存 保存函数计算结果
重试机制 失败后重新执行函数
Web 路由 将 URL 和处理函数绑定

计时装饰器示例:

import time
from functools import wraps


def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()

        print(f"运行时间:{end - start:.6f} 秒")
        return result

    return wrapper

Ⅶ. 异常处理 try-except

一、异常和常见类型

异常是程序运行过程中出现的错误。

num = int("abc")  # ValueError
类型 说明 示例
语法错误 代码不符合 Python 语法 缺少冒号、缩进错误
运行时异常 语法正确,但运行时出错 除数为 0、文件不存在

常见异常:

异常 常见原因
SyntaxError 语法错误
IndentationError 缩进错误
NameError 使用未定义的变量
TypeError 数据类型不符合要求
ValueError 数据类型正确,但值不符合要求
IndexError 序列索引越界
KeyError 字典中不存在指定 key
AttributeError 对象不存在指定属性或方法
ZeroDivisionError 除数为 0
FileNotFoundError 文件不存在
ModuleNotFoundError 找不到指定模块

二、try…except

基本结构:

try:
    可能出现异常的代码
except 异常类型:
    异常处理代码

示例:

try:
    num = int(input("请输入一个数字:"))
    print(10 / num)
except ValueError:
    print("输入的内容不是整数")
except ZeroDivisionError:
    print("除数不能为0")
情况 执行结果
try 中没有异常 跳过所有 except
出现匹配的异常 执行对应的 except
出现未处理的异常 程序仍会终止

一个 except 可以捕获多个异常:

try:
    num = int(input("请输入一个数字:"))
    print(10 / num)
except (ValueError, ZeroDivisionError):
    print("输入错误")

也可以使用 Exception 捕获大多数常规异常:

try:
    code
except Exception:
    print("程序出现异常")

建议优先捕获明确异常,并将范围更大的 Exception 放在最后。

三、获取异常信息

使用 as 获取异常对象:

try:
    print(10 / 0)
except ZeroDivisionError as error:
    print("异常类型:", type(error))
    print("异常信息:", error)

查看完整调用链:

import traceback

try:
    print(10 / 0)
except ZeroDivisionError:
    traceback.print_exc()
方式 作用
str(error) 获取异常描述
type(error) 获取异常类型
traceback.print_exc() 输出完整异常调用链

四、else 和 finally

完整结构:

try:
    可能出现异常的代码
except 异常类型:
    异常处理代码
else:
    没有异常时执行的代码
finally:
    无论是否异常都会执行的代码
结构 执行时机
try 执行可能出现异常的代码
except 捕获到匹配异常时执行
else try 没有出现异常时执行
finally 无论是否出现异常都会执行
try:
    num = int(input("请输入一个数字:"))
    result = 10 / num
except ValueError:
    print("请输入整数")
except ZeroDivisionError:
    print("除数不能为0")
else:
    print(f"计算结果:{result}")
finally:
    print("程序执行结束")

finally 常用于关闭文件、数据库连接、网络连接或释放锁。操作文件时更推荐使用 with

try:
    with open("data.txt", "r", encoding="utf-8") as file:
        print(file.read())
except FileNotFoundError:
    print("文件不存在")

五、主动抛出异常 raise

raise 可以根据业务规则主动抛出异常。

def set_age(age):
    if age < 0:
        raise ValueError("年龄不能小于0")
    return age
写法 作用
raise ValueError("信息") 创建并抛出指定异常
raise error 重新抛出已有异常对象
raise except 中继续抛出当前异常

捕获后重新抛出:

def calculate():
    try:
        return 10 / 0
    except ZeroDivisionError:
        print("记录异常日志")
        raise

六、自定义异常

当内置异常不能准确表达业务问题时,可以自定义异常。自定义异常通常继承 Exception

class AgeError(Exception):
    pass


def register(age):
    if age < 18:
        raise AgeError("年龄不足18岁,不能注册")
    print("注册成功")


try:
    register(16)
except AgeError as error:
    print(error)

自定义异常也可以保存更多数据:

class AgeError(Exception):
    def __init__(self, age, message="年龄不符合要求"):
        self.age = age
        super().__init__(f"{message}{age}")
使用场景 示例
参数校验 年龄、金额、日期不符合要求
权限判断 用户无权执行操作
业务状态 余额不足、库存不足
数据校验 数据缺失或格式错误

七、异常处理建议

建议 说明
只包裹可能出错的代码 避免 try 范围过大
捕获明确异常 更容易定位问题
不要使用空 except 容易隐藏真正的错误
不要静默忽略异常 至少记录日志或说明原因
使用 finallywith 清理资源 防止资源泄漏
业务错误使用自定义异常 让异常含义更明确
不要用异常代替正常判断 可预期情况优先使用条件语句

不推荐:

try:
    code
except:
    pass

推荐:

try:
    code
except ValueError as error:
    print(f"数据格式错误:{error}")

Ⅷ. 常见的4种设计模式(单例/工厂/策略/观察者)

程序中设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,这些解决方案是众多软件按开发人员经过相当长的一段时间的实验和错误总结出来的。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码的可靠性。

一、单例模式

标题 解释
使用 该模式的目的是确保某一个类只有一个实例存在
适用场景 当一个类只能有一个实例而客户可以从一个众所周知的访问点访问它时
优点 对唯一实例的受控访问,相当于全局变量,但是又可以防止此变量被篡改
class A:
    a = None

    def __new__(cls, *args, **kwargs):
        if cls.a is None:
            cls.a = object.__new__(cls)
        return cls.a

    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        print(self.name)


if __name__ == '__main__':
    a = A('张三')
    b = A('李四')
    a()
    b()


结果:

在这里插入图片描述
图示一下:

在这里插入图片描述

二、工厂模式

标题 解释
使用 定义一个创建对象的接口,让子类决定实例化哪个接口
适用场景 需要生产多种,大量复杂对象的时候,需要降低代码耦合度的时候,当系统中的产品类经常需要扩展的时候
优点 每个具体的产品都对应一个具体工厂,不需要修改工厂类的代码,工厂类可以不知道它所创建的具体的类,隐藏了对象创建的实现细节
缺点 每增加一个具体的产品类,就必须增加一个相应的工厂类

代码示例:

class BC:
    def run(self):
        print('奔驰在此')


class AD:
    def run(self):
        print('奥迪在此')


class BMW:
    def run(self):
        print('宝马在此')


class Factory:
    def whatcar(self, name):
        if name == '奔驰':
            return BC()
        elif name == '奥迪':
            ad = AD()
            return ad
        else:
            return BMW()


if __name__ == '__main__':
    a = Factory().whatcar('奔驰').run()
    b = Factory().whatcar('宝马').run()
    c = Factory().whatcar('奥迪').run()

结果:
在这里插入图片描述

三、策略模式

标题 解释
使用 定义一系列的算法把它们一个个封装起来,并且使它们可相互替换.该模式使得算法可独立于使用它的客户而变化
适用场景: 许多相关的类仅仅是行为有异,需使用一个算法的不同变体,算法使用了客户端无需知道的数据,一个类中的多个行为以多个条件语句存在可以将其封装在不同的策略类中
优点 定义了一系列可重用的算法和行为,消除了一些条件语句,可提供相同行为的不同实现
缺点 客户必须了解不同的策略,策略与上下文之间的通信开销,增加了对象的数目

代码示例:

class c1:
    def go(self):
        return 1


class c2:
    def go(self):
        return 2


if __name__ == '__main__':
    type = int(input('请输入要用的算法(1/2):'))
    if type == 1:
        print(c1().go())
    elif type == 2:
        print(c2().go())

结果:
在这里插入图片描述

四、观察者模式

标题 解释
定义: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并被自动更新.观察者模式又称为’发布订阅’模式
适用场景: 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,这时候将两者封装在独立的对象中以使它们各自独立的改变和复用 ① 当一个对象的改变需要同时改变其他对象,而且不知道具体有多少对象以待改变 ②当一个对象必须通知其他对象,而又不知道其他对象是谁,即这些对象之间是解耦的
优点: 目标和观察者之间的耦合最小,支持广播通信
缺点: 多个观察者之间互不知道对方的存在,因此一个观察者对主题的修改可能造成错误的更新
class Boss:
    def __init__(self):
        self.observers = []
        self.command = ''

    def attach(self, ob):
        self.observers.append(ob)

    def notify(self):
        for ob in self.observers:
            ob.message()  # 调用对象的update方法


class Employee():
    def __init__(self, name, boss):
        self.name = name
        self.boss = boss

    def message(self):
        print(f'{self.name},你的老板发来命令:{self.boss.command}')


if __name__ == '__main__':
    boss = Boss()
    zs = Employee('张三', boss)
    boss.attach(zs)
    boss.command = '你被开除了'
    boss.notify()

结果:
在这里插入图片描述


Ⅸ. 网络编程

一、网络、IP地址和其他网络专业名词

1.网络

  • 定义:用来做设备和设备之间的链接工具 目的就是为了数据共享,在网络传输数据据时需要使用一些介质和一些协议
  • 介质:光纤、网线
  • 协议:指的是计算机网络中互相通信的对等实体之间交换信息时所必须遵守的规则的集合,TCP/IP是因特网的正式网络协议,TCP/IP是由一组具有专业用途的多个子协议组合而成的,这些子协议包括TCP、IP、UDP、ARP、ICMP等。

2.IP地址

名称 解释
作用 给因特网上的每台计算机和其它设备都规定了一个唯一的地址
ip地址类型 1.公有地址:由Inter NIC(Internet Network Information Center因特网信息中心)负责。这些IP地址分配给注册并向Inter NIC提出申请的组织机构。通过它直接访问因特网
2.私有地址(Private address)属于非注册地址,专门为组织机构内部使用。
端口号 - 端口号范围:0-65535
- 知名端口号:0-1023
- 注册端口号:1024-49151
- 动态端口号(私有端口号):49152-65535

动态端口号:当我们启动一个网络应用程序,如果不指定端口号,会默认向系统申请一个随机的端口号,当应用程序结束,系统会将端口回收,给下一个应用程序使用

常用端口号 对应服务
22 ssh服务端口号
3306 mysql默认端口号
80 HTTP端口号
6379 redis服务默认端口号

3.其他的一些网络专业术语

名词 解释
IP(互联网协议地址)IP 用来标识网络中的一个通信实体的地址。互联网之间的通信相当于快递收发,需要知道每个电脑的详细地址才能实现数据的准确收发。2.广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示。范围: 0.0.0.0-255.255.255.255
子网 子网就是一个网中一个比较低级的网
子网掩码 子网掩码用来确定一个子网中的IP地址及数量,一个子网节点IP地址与子网掩码相与运算得到该子网下的IP地址。一般的子网掩码为255.255.255.0。通过子网掩码确定两个IP地址是否属于同一个子网。
网关 网络络关卡口的简称。他的作用就是:链接两个不同的网络,比如联通公司给你家装了宽带(相当于给你家装了一个网关),你家里的所有设备都是在一个局域子网中,这个局域网和互联网之间使用网关进行连接。
交换机 交换机的作用就是分发数据,为设备提供IP地址。交换机直接与设备的网卡连接,数据通过指定的端口发送到指定的设备上,交换机只关心与其联系的设备的mac地址。
路由器 路由器具备WAN口和多个LAN口。它实际上是**网关和交换机的结合体。**wan口与宽带公司机房相连,LAN口与局域网设备相连。
路由 数据从一台电脑发送到另一台电脑的时所走过的路线就叫做路由。
端口 同一台设备下有很多的应用程序,但是网卡只有一个,数据通过网卡获得和发送,如何确定接收的数据到底是哪一个程序的呢?发给QQ的数据不可能被微信接收。利用端口可以解决这个问题。
mac地址 每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
局域网(Local Area Network,LAN) 指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的日程安排、电子邮件和传真通信服务等功能。局域网是封闭型的,可以由办公室内的两台计算机组成,也可以由一个公司内的上千台计算机组成。

二、软件的开发架构(c/s架构和b/s架构)


了解的涉及到两个程序之间通讯的应用大致可以分为两种:

  • 应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用
  • web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用

这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~

1.C/S架构
C/S架构:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
​​​​在这里插入图片描述

这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

2.B/S架构
B/S架构:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
在这里插入图片描述

浏览器其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的网页资源,客户端浏览器就能进行增删改查。

三、OSI模型

互联网的核心就是由一堆协议组成,协议就是标准。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。人们按照分工不同把互联网协议从逻辑上划分了层级:

在这里插入图片描述
每一层都有自己的好多协议,这里有个神图,大概了解一下
在这里插入图片描述
这里我们重点看一下传输层的TCP和UDP:

TCP UDP
简介 Transmission Control Protocol ,传输控制协议 User Data Protocol ,用户数据报协议
优点 一个可靠的、面向连接的协议 传输效率高(发送前时延小),支持一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制
缺点 传输效率低、面向字节流 不可靠的、无连接的协议
应用 Web浏览器;电子邮件、文件传输程序 域名系统 (DNS);视频流;IP语音(VoIP)

TCP的三次握手

TCP是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。 而一个TCP 连接必须要经过三次 “ 对话 ” 才能建立起 来,其中的过程非常复杂,图示如下:
​​​​在这里插入图片描述

简单描述一下:

  1. 第一步,客户端发送一个包含SYN 即同步( Synchronize )标志的 TCP 报文, SYN 同步报文会指明客户端使用的端口以及TCP 连接的初始序号。
  2. 第二步,服务器在收到客户端的SYN 报文后,将返回一个 SYN+ACK 的报文,表示客户端的请求被接受,同时TCP 序号被加一, ACK 即确认( Acknowledgement )
  3. 第三步,客户端也返回一个确认报文ACK 给服务器端,同样 TCP 序列号被加一,到此一个 TCP 连接完成。然后才开始通信的第二步:数据处理。

TCP的四次挥手

断开链接也需要进行四次挥手操作,图示如下:
在这里插入图片描述
简单描述就是:

  1. 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP 连接的请求 ;
  2. 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
  3. 由B 端再提出反方向的关闭请求, 将 FIN 置 1 ;
  4. 主机A对主机B 的请求进行确认,将 ACK 置 1 ,双方向的关闭结束 。

四、socket套接字编程

1.socket编程过程

Socket 是传输层供给应用层的编程接口,所以Socket 编程就分为 TCP 编程和 UDP 编程两类。
在这里插入图片描述
2.python中的socket编程
Python 中,我们用 socket() 函数来创建套接字。

服务器端套接字函数

函数 描述
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来

客户端套接字函数

函数 描述
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

函数 描述
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果 flag 为 False,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用 recv() 没有发现任何数据,或 send() 调用无法立即发送数据,那么将引起 socket.error 异常。
s.makefile() 创建一个与该套接字相关连的文件

我们来试验一下:
server.py

import socket

# 创建 socket 对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()

# 绑定端口号
serversocket.bind((host, 9999))

# 设置最大连接数,超过后排队
serversocket.listen(5)

while True:
    # 建立客户端连接
    clientsocket, addr = serversocket.accept()

    print("连接地址: %s" % str(addr))

    msg = '我是server.py文件' + "\r\n"
    clientsocket.send(msg.encode('utf-8'))
    clientsocket.close()

client.py

# 导入 socket、sys 模块
import socket

# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()

# 连接服务,指定主机和端口
s.connect((host, 9999))

# 接收小于 1024 字节的数据
msg = s.recv(1024)

s.close()

print(msg.decode('utf-8'))

运行这两个文件,会在client.py运行结果看到:

在这里插入图片描述


Ⅹ. 进程、线程和协程

一、并发和并行

概念 说明 示例
并发 多个任务在同一时间段内交替推进 单核 CPU 快速切换多个任务
并行 多个任务在同一时刻真正同时执行 多核 CPU 同时执行多个进程

并发强调的是任务处理能力,并行强调的是任务是否真正同时执行。

二、 同步、异步、阻塞和非阻塞

概念 说明
同步 发起任务后,按照流程等待或检查任务结果
异步 发起任务后先执行其他工作,结果完成后再处理
阻塞 当前执行单元因为等待而暂停运行
非阻塞 当前执行单元无需停在原地等待

同步和异步描述的是任务结果的通知方式,阻塞和非阻塞描述的是等待期间的状态,两组概念不能简单画等号。

三、CPU 密集型和 I/O 密集型

类型 特点 常见任务
CPU 密集型 大部分时间用于计算 图像处理、数据压缩、复杂运算
I/O 密集型 大部分时间用于等待外部资源 网络请求、文件读写、数据库操作

四、进程

进程是操作系统分配资源和进行调度的重要单位。不同进程默认拥有独立的内存空间,一个进程崩溃通常不会直接破坏另一个进程的数据。

1. multiprocessing.Process

from multiprocessing import Process
import os


def task(name):
    print(f"任务:{name},进程ID:{os.getpid()}")


if __name__ == "__main__":
    process = Process(target=task, args=("下载文件",))
    process.start()
    process.join()

    print("主进程结束")
方法/属性 作用
start() 启动子进程
join() 等待子进程结束
is_alive() 判断进程是否仍在运行
pid 获取进程 ID
terminate() 请求终止进程,应谨慎使用

在 Windows 和使用 spawn 启动方式的平台上,创建进程的代码必须放在:

if __name__ == "__main__":

否则可能重复创建子进程或直接报错。

2. 进程池

频繁创建和销毁进程开销较大,可以使用进程池复用工作进程。

from concurrent.futures import ProcessPoolExecutor


def square(num):
    return num * num


if __name__ == "__main__":
    with ProcessPoolExecutor(max_workers=4) as executor:
        results = executor.map(square, range(10))
        print(list(results))

相比手动管理多个 ProcessProcessPoolExecutor 通常更简洁。

3. 进程间通信

不同进程默认不能直接访问彼此的普通变量,需要通过进程间通信交换数据。

工具 作用
Queue 安全地传递队列数据
Pipe 在两个连接端之间传递数据
ValueArray 共享简单数据
Manager 共享字典、列表等代理对象
Lock 控制多个进程访问共享资源

使用 Queue

from multiprocessing import Process, Queue


def producer(queue):
    queue.put("hello")


if __name__ == "__main__":
    queue = Queue()
    process = Process(target=producer, args=(queue,))

    process.start()
    print(queue.get())
    process.join()

4. 进程的优缺点

优点 缺点
可以利用多核 CPU 创建和切换成本较高
内存相互隔离,稳定性较好 进程间通信更复杂
可以绕过默认 CPython 的 GIL 限制 占用内存较多

五、线程

线程是进程中的执行单元。同一进程内的线程共享内存和文件等资源,因此通信方便,但也会带来数据竞争问题。

1. threading.Thread

import threading
import time


def task(name):
    print(f"{name} 开始")
    time.sleep(1)
    print(f"{name} 结束")


threads = []

for i in range(3):
    thread = threading.Thread(target=task, args=(f"任务{i}",))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

print("所有任务结束")
方法/属性 作用
start() 启动线程,只能调用一次
join() 等待线程结束
is_alive() 判断线程是否仍在运行
name 线程名称
daemon 是否为守护线程

不建议依赖守护线程处理文件写入、数据库事务等重要清理工作,因为程序退出时守护线程可能被突然停止。

2. 线程池

from concurrent.futures import ThreadPoolExecutor
import time


def task(num):
    time.sleep(1)
    return num * num


with ThreadPoolExecutor(max_workers=5) as executor:
    results = executor.map(task, range(10))
    print(list(results))

线程池适合执行数量较多的短任务,并能限制同时运行的线程数量。

六、GIL

GIL 是 CPython 中的全局解释器锁。在默认启用 GIL 的 CPython 构建中,同一进程内通常只有一个线程可以在某个时刻执行 Python 字节码。

1. GIL 的影响

场景 影响
CPU 密集型 Python 代码 多线程通常无法获得理想的多核并行加速
I/O 密集型代码 等待 I/O 时线程可以切换,多线程仍然有效
C 扩展计算 某些扩展会主动释放 GIL,可能实现并行计算
多进程 每个进程拥有独立解释器,可以利用多核

因此不能简单地说“Python 多线程没有用”。更准确的说法是:

默认 CPython 中,多线程适合 I/O 密集型任务;CPU 密集型纯 Python 代码通常更适合多进程。

2. Free-threaded Python

Python 3.13 起提供可选的 free-threaded 构建,可以禁用 GIL,让线程并行执行 Python 代码。

需要注意:

  • free-threaded 构建不是默认模式;
  • 第三方扩展需要兼容 free-threaded 模式;
  • 没有 GIL 不代表共享数据自动安全;
  • 多线程程序仍然需要锁等同步机制保护共享状态。

七、线程安全和锁

多个线程同时修改共享数据时,可能发生数据竞争,导致结果不符合预期。

import threading

count = 0
lock = threading.Lock()

def add():
    global count

    for _ in range(100000):
        with lock:
            count += 1

with lock 会自动完成加锁和释放锁:

with lock:
    修改共享数据

常见同步工具:

工具 作用
Lock 普通互斥锁
RLock 同一线程可以重复获取的锁
Semaphore 限制同时访问资源的线程数量
Event 在线程之间发送状态通知
Condition 等待某个条件成立
Barrier 等待多个线程到达同一位置
queue.Queue 线程安全的任务或数据队列

1. Queue 线程通信

生产者和消费者场景中,优先使用 queue.Queue,不要手动共享普通列表。

from queue import Queue
import threading


queue = Queue()


def producer():
    for i in range(5):
        queue.put(i)
    queue.put(None)


def consumer():
    while True:
        item = queue.get()

        try:
            if item is None:
                break
            print(f"处理:{item}")
        finally:
            queue.task_done()


t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t1.start()
t2.start()

t1.join()
queue.join()
t2.join()

2. 死锁

死锁是多个执行单元互相等待对方释放资源,导致程序无法继续执行。

常见原因:

  • 多把锁的获取顺序不一致;
  • 获取锁后没有释放;
  • 当前线程等待自己持有的普通锁;
  • 持锁期间执行耗时或阻塞操作。

避免方式:

方法 说明
使用 with lock 确保异常时也能释放锁
固定加锁顺序 多把锁始终按相同顺序获取
缩小临界区 锁内只保留必要操作
使用超时 避免永久等待
优先使用线程安全队列 减少手动共享状态

八、协程和 asyncio

协程是可以暂停和恢复的函数。asyncio 使用事件循环调度协程,适合处理大量 I/O 等待任务。

1. async 和 await

使用 async def 定义协程函数:

import asyncio


async def task():
    print("开始")
    await asyncio.sleep(1)
    print("结束")


asyncio.run(task())

调用协程函数不会直接执行函数体,而是返回协程对象。通常由 awaitasyncio.run() 或任务进行调度。

关键字/函数 作用
async def 定义协程函数
await 等待可等待对象,并让出执行权
asyncio.run() 创建事件循环并运行入口协程
asyncio.create_task() 将协程包装成任务并调度
asyncio.gather() 并发等待多个可等待对象

2. 创建并发任务

顺序执行:

import asyncio


async def work(name, delay):
    await asyncio.sleep(delay)
    return name


async def main():
    result1 = await work("任务1", 1)
    result2 = await work("任务2", 1)
    print(result1, result2)


asyncio.run(main())

并发执行:

import asyncio


async def work(name, delay):
    await asyncio.sleep(delay)
    return name


async def main():
    task1 = asyncio.create_task(work("任务1", 1))
    task2 = asyncio.create_task(work("任务2", 1))

    result1 = await task1
    result2 = await task2
    print(result1, result2)


asyncio.run(main())

使用 gather()

async def main():
    results = await asyncio.gather(
        work("任务1", 1),
        work("任务2", 1),
        work("任务3", 1),
    )
    print(results)

3. TaskGroup

较新的 Python 版本可以使用 TaskGroup 管理一组相关任务。它提供结构化并发:离开代码块前会等待所有任务结束,某个任务失败时也能统一处理其他任务。

import asyncio


async def work(name, delay):
    await asyncio.sleep(delay)
    return name


async def main():
    async with asyncio.TaskGroup() as group:
        task1 = group.create_task(work("任务1", 1))
        task2 = group.create_task(work("任务2", 1))

    print(task1.result(), task2.result())


asyncio.run(main())

九、协程中的阻塞问题

事件循环通常运行在一个线程中。如果协程内部执行长时间阻塞代码,会卡住整个事件循环。

错误示例:

import time


async def task():
    time.sleep(5)  # 阻塞事件循环

异步等待应使用:

await asyncio.sleep(5)

如果必须调用阻塞函数,可以使用 asyncio.to_thread()

import asyncio
import time


def blocking_task():
    time.sleep(2)
    return "完成"


async def main():
    result = await asyncio.to_thread(blocking_task)
    print(result)


asyncio.run(main())

to_thread() 主要适合把阻塞式 I/O 函数放到工作线程中执行。CPU 密集型纯 Python 任务通常应使用进程池。

十、协程超时、取消和异常

1. 超时控制

import asyncio


async def work():
    await asyncio.sleep(5)


async def main():
    try:
        async with asyncio.timeout(1):
            await work()
    except TimeoutError:
        print("任务超时")


asyncio.run(main())

2. 取消任务

async def main():
    task = asyncio.create_task(work("任务", 10))
    await asyncio.sleep(1)
    task.cancel()

    try:
        await task
    except asyncio.CancelledError:
        print("任务已取消")

协程执行资源清理时,应使用 try...finally

async def work():
    try:
        await asyncio.sleep(10)
    finally:
        print("清理资源")

十一、如何选择

问题 选择建议
是否主要进行大量计算? 使用多进程
是否调用阻塞式 I/O 库? 使用多线程
库是否原生支持 async/await 使用协程
是否需要进程隔离? 使用多进程
是否有大量并发连接? 优先考虑协程
任务规模是否很小? 普通同步代码可能最简单

常见组合方式:

组合 场景
asyncio + to_thread() 异步程序中调用阻塞式 I/O
asyncio + 进程池 异步程序中执行 CPU 密集型任务
线程池 + queue.Queue 生产者消费者模型
多进程 + 每进程一个事件循环 多核与大量异步 I/O 结合

不要为了“并发”而并发。并发模型会增加调试、异常处理、资源清理和数据一致性的复杂度。

十二、常见误区

误区 正确理解
多线程一定更快 线程创建、调度和同步都有成本
Python 线程完全不能并行 默认 CPython 的 Python 字节码受 GIL 限制;I/O、部分 C 扩展和 free-threaded 构建情况不同
有 GIL 就不需要加锁 多步共享状态操作仍可能发生竞争
协程会自动并发 需要创建任务或使用并发等待工具
async 函数中代码都不会阻塞 普通阻塞函数仍会阻塞事件循环
协程适合 CPU 密集型任务 协程主要解决 I/O 等待问题
进程变量可以直接共享 进程默认内存隔离,需要 IPC
守护线程会安全完成清理 程序退出时守护线程可能被突然停止

十三、进程、线程和协程对比

对比项 进程 线程 协程
调度者 操作系统 操作系统 事件循环/程序代码
内存空间 默认相互独立 共享所属进程内存 共享所属线程内存
创建开销 较大 较小 很小
切换开销 较大 中等 较小
数据通信 需要 IPC 可直接共享数据 可直接共享数据
隔离性 较强 较弱 较弱
适合场景 CPU 密集型 阻塞式 I/O 密集型 大量异步 I/O
能否利用多核 可以 默认 CPython 的 CPU 密集型受 GIL 限制 单事件循环通常运行在单线程中

简单选择:

场景 推荐方式
大量计算任务 多进程、ProcessPoolExecutor
网络请求、文件读写等阻塞 I/O 多线程、ThreadPoolExecutor
大量连接且库支持异步 asyncio 协程
少量简单任务 优先使用普通同步代码

更多推荐