Python蟒蛇书笔记+自己笔记

文章目录

  • [1 Python 基本数据类型](#1 Python 基本数据类型)
    • [1.1 字符串 ""](#1.1 字符串 "")
    • [1.2 列表 ,](#1.2 列表 [,])
    • [1.3 元组 tuple (,)](#1.3 元组 tuple (,))
    • [1.4 集合 Set {""}](#1.4 集合 Set {""})
    • [1.5 字典 {"","}](#1.5 字典 {"","})
    • [1.6 常用函数](#1.6 常用函数)
    • [1.7 函数&方法 def](#1.7 函数&方法 def)
    • [1.8 模块 module](#1.8 模块 module)
    • [1.9 类 class](#1.9 类 class)
    • [1.10 文件&文件夹 pathlib.Path](#1.10 文件&文件夹 pathlib.Path)
      • [1.10.1 reader 迭代器(多类型通用)](#1.10.1 reader 迭代器(多类型通用))
      • [1.10.2 path.open+reader改进,减少内存开销](#1.10.2 path.open+reader改进,减少内存开销)
    • [1.11 json数据获取与使用](#1.11 json数据获取与使用)
      • [1.11.1 核心知识点](#1.11.1 核心知识点)
      • [1.11.2 GeoJSON](#1.11.2 GeoJSON)
    • [1.12 异常处理 traceback](#1.12 异常处理 traceback)
    • [1.13 测试 pytest](#1.13 测试 pytest)
  • [2 Pygame 外星人入侵游戏](#2 Pygame 外星人入侵游戏)
  • [3 Matplotlib 静态图](#3 Matplotlib 静态图)
    • [3.1 折线图](#3.1 折线图)
      • [3.1.1 单数据折线图](#3.1.1 单数据折线图)
      • [3.2.2 双数据折线图](#3.2.2 双数据折线图)
    • [3.2 散点图](#3.2 散点图)
      • [3.2.1 pyplot简单散点图](#3.2.1 pyplot简单散点图)
      • [3.2.2 pyplot详细散点图](#3.2.2 pyplot详细散点图)
      • [3.2.3 plotly简单散点图](#3.2.3 plotly简单散点图)
      • [3.2.4 plotly复杂散点图](#3.2.4 plotly复杂散点图)
    • [3.3 随机游走](#3.3 随机游走)
      • [3.3.1 普通随机游走](#3.3.1 普通随机游走)
      • [3.3.2 复杂随机游走](#3.3.2 复杂随机游走)
  • [4 Plotly 动态图](#4 Plotly 动态图)
    • [4.1 简单动态图](#4.1 简单动态图)
  • [5 csv文件处理](#5 csv文件处理)
    • [5.1 简单读取csv](#5.1 简单读取csv)
    • [5.2 datetime 时间格式](#5.2 datetime 时间格式)
      • [5.2.1 相关参数 :---:](#5.2.1 相关参数 :---:)
      • [5.2.2 datetime.strftime(格式str)](#5.2.2 datetime.strftime(格式str))
      • [5.2.3 datetimes.strptime(时间str,格式str)](#5.2.3 datetimes.strptime(时间str,格式str))
  • 6 ##########API
  • [########## TimeStamp 时间格式](########## TimeStamp 时间格式)
  • [########## pandas 数据分析库](########## pandas 数据分析库)
    • [## 简单使用](## 简单使用)
    • [# Series 列/一维数组](# Series 列/一维数组)
  • [# ds版本pandas](# ds版本pandas)
  • [🐼 Pandas 完全学习指南](#🐼 Pandas 完全学习指南)
    • [1. 认识 Pandas 和安装](#1. 认识 Pandas 和安装)
    • [2. 核心数据结构](#2. 核心数据结构)
      • [2.1 Series 系列](#2.1 Series 系列)
      • [2.2 DataFrame 数据框](#2.2 DataFrame 数据框)
    • [3. 数据读取与写入](#3. 数据读取与写入)
      • [3.1 读取 CSV](#3.1 读取 CSV)
      • [3.2 读取 Excel](#3.2 读取 Excel)
      • [3.3 写入文件](#3.3 写入文件)
    • [4. 数据查看与选择(极其重要)](#4. 数据查看与选择(极其重要))
      • [4.1 查看前几行/后几行](#4.1 查看前几行/后几行)
      • [4.2 选择列](#4.2 选择列)
      • [4.3 选择行:`loc` 与 `iloc`](#4.3 选择行:lociloc)
      • [4.4 条件选择(布尔索引)](#4.4 条件选择(布尔索引))
      • [4.5 行列同时选择](#4.5 行列同时选择)
    • [5. 数据清洗](#5. 数据清洗)
      • [5.1 处理缺失值](#5.1 处理缺失值)
      • [5.2 处理重复值](#5.2 处理重复值)
      • [5.3 数据类型转换](#5.3 数据类型转换)
      • [5.4 列名与索引处理](#5.4 列名与索引处理)
      • [5.5 异常值处理](#5.5 异常值处理)
    • [6. 数据变换与操作](#6. 数据变换与操作)
      • [6.1 排序](#6.1 排序)
      • [6.2 增加列/修改列](#6.2 增加列/修改列)
      • [6.3 删除列](#6.3 删除列)
      • [6.4 应用函数](#6.4 应用函数)
      • [6.5 映射与替换](#6.5 映射与替换)
    • [7. 分组与聚合](#7. 分组与聚合)
    • [8. 合并与连接](#8. 合并与连接)
      • [8.1 纵向拼接 `concat`](#8.1 纵向拼接 concat)
      • [8.2 横向拼接 `merge`(类似 SQL JOIN)](#8.2 横向拼接 merge(类似 SQL JOIN))
      • [8.3 `join` 按索引连接](#8.3 join 按索引连接)
    • [9. 时间序列处理](#9. 时间序列处理)
      • [9.1 生成时间序列](#9.1 生成时间序列)
      • [9.2 转换时间格式](#9.2 转换时间格式)
      • [9.3 时间索引的方便功能](#9.3 时间索引的方便功能)
      • [9.4 提取时间特征](#9.4 提取时间特征)
    • [10. 常用数据分析与统计](#10. 常用数据分析与统计)
    • [11. 可视化(简易)](#11. 可视化(简易))
    • [12. 性能优化小技巧](#12. 性能优化小技巧)
    • [13. 实战小案例](#13. 实战小案例)
    • [14. 学习建议与资源](#14. 学习建议与资源)
    • [15. 总结](#15. 总结)

1 Python 基本数据类型

1.1 字符串 ""

  • 尽量避免使用 += 拼接字符串,会创建很多新对象
python 复制代码
str = "AA BB cc dd"


# 每个单词首字母大写,其余字母小写。
str.title()
# 全部单词大写&小写
str.upper()
str.lower()


# 字符串切片,与列表一样 [左闭右开)
str[0:4]	# "AA B"
# 字符串反转
str[::-1]


# 字符串拆分,无参数=默认按任意空白字符(空格,换行,制表符)来分割
str.split()		# AA BB
str.split(" ")	# AA BB


# join:用分隔符连接一个可迭代对象中的每个元素
	# A.join(B),吧AA当成分隔符拼接到B中
	list1 = str.split(" ")		# ["AA","BB","cc","dd"] 
	# 分隔符拼接列表
    "~".join(list1)				# "AA~BB~cc~dd"
    "".join(["AA","BB","cc"])	# "AABBcc"
	# 字符串插入字符串
    "AA".join("BBB")			# "BAABAAB"


# 删除左右两侧空格
str.strip()	# 删除两侧
str.lstrip()# 只删除左侧
str.rstrip()# 只删除右侧
# 删除前缀 & 后缀
print(str.removeprefix("前缀str1"))
print(str.removesuffix("后缀str2"))


# 字符串替换
str2 = str1.replace("oldStr","newStr")
str2 = str1.replace("oldStr","newStr",替换次数num)


# 字符串格式化输出
# f-string格式化,最推荐
str = f"XXX {p1} xxx {p2} ";
name,age = "Tom",18
print(f"{name:>10}")   # 右对齐,宽度10
print(f"{age:03d}")    # 数字补零 → "018"
print(f"{3.14159:.2f}")# 保留两位小数 → "3.14"
    
    
# .format(),兼容性好
print("{} is {}".format("Age", 18))
# 制表符 \t 效果就是键盘上的Tab按键一次,对齐作用。
# 换行符 \n
# 字符串str->字节bytes,无参数默认'utf-8'
str.encode("utf-8")
# 字节bytes->字符串str
bytes.decode("utf-8")

1.2 列表 ,

  • 列表 \[\] 是有序,可变集合,set {} 是无序集合,索引从0开始。
  • 索引是负数表示从右开始,最右的个索引是-1
  • append()extend()insert()sort()修改列表的方法 ,返回的都是 None
    • my_list.sort()原地排序,永久改变,无返回值
    • sorted(my_list)不改变my_list,有返回值
python 复制代码
# 创建列表
my_list = []
my_list = list("abc")					# 字符串转列表['a','b','c']
my_list = list(range(5))				# [0,1,2,3,4]
my_list = [num for num in range(5)]	# [0,1,2,3,4]
my_list = ["111","222","aaa",[1,2]]	# 列表可以混搭


# 列表末尾添加元素
	# append(),把参数作为一个整体添加到末尾
    my_list.append(元素)
    # extend()传入一个可迭代对象,然后拆开添加
    my_list = [1, 2, 3];
	my_list.extend([4, 5, 6]) # 添加列表中的每个元素
	print(my_list)  # [1, 2, 3, 4, 5, 6]   ← 扁平化,不嵌套!
	my_list.extend('abc')     # 字符串也是可迭代的,拆成单个字符
	print(my_list)  # [1, 2, 3, 4, 5, 6, 'a', 'b', 'c']
	my_list.extend((7, 8))        # 元组也能拆开
	print(my_list)  # [1, 2, 3, 4, 5, 6, 'a', 'b', 'c', 7, 8]

    
# 列表指定位置插入,元素后移
my_list.insert(位置索引,元素);
# 列表推导式
my_list=[i**2 for i in range(1,11)]	# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 列表推导式+筛选条件
my_list = [x**2 for x in range(10) if x%2==0]  # [0,4,16,36,64]
# 列表推导式+循环嵌套


# 删除指定索引元素
del my_list[位置索引]		# 无返回值
del my_list[0:3]    		# 删除切片 
my_list.pop(位置索引)		# 有返回值 
# 删除列表末尾元素,同时返回被删除的元素
my_list.pop()			# 无参数表示删除最后一个
# 根据元素值删除指定元素
my_list.remove(元素值)	# 只删除第一个,后面重复值不删除
# 清空所有元素
my_list.clear()


# 修改元素
my_list[0]	= newObj
# 切片赋值,新切片代替部分长度
my_list[1:3] = [1,2,3,4,5,6]


# 永久排序,改变原始数据
my_list.sort()
my_list.sort(reverse=True)	# 倒序排列
# 临时排序,不改变原始顺序,返回排序后的副本
sorted(my_list)


# 永久反转元素排序(后队变前队)
my_list.reverse();
# 列表长度
len(my_list)
# 判断是否包含指定元素
obj in my_list		# True/False
# count()统计次数
my_list = [1, 2, 3, 2, 4, 2, 5]
count_2 = my_list.count(2)   # count_2=3
count_9 = my_list.count(9)   # count_9=0,不存在元素不报错
# index()查找索引,查找元素第一次出现的索引位置
# 如果元素不存在,会报错 ValueError
my_list = [10, 20, 30, 20, 40]
index_30 = my_list.index(30)   # index_30索引=2
index_20 = my_list.index(20)   # 如果有多个,只返回第一个index_20=1(不是3)
my_list.index(99)  	# 报错:ValueError: 99 is not in list


# 列表切片/裁切
my_list = [1,2,3,'abc']
my_list[2]		# 3
my_list[-3]	# 2
my_list[1:3]	# [2,3]
my_list[::-1]	# ['abc',3,2,],列表反转
my_list[起始角标:终止角标+1]	# 左闭右开,获取角标1~3的元素


# for循环遍历切片
for i in my_list[2:5]: # 遍历角标2-4的元素
    print(i)


# 列表拼接
my_list1.extend(my_list2)		# my_list1 被修改
my_list3 = my_list1 + my_list2	# 返回新列表

# 列表复制
    # 切片方式
    my_list1=my_list[:]
    # copy()
    my_list1=my_list.copy()
    
    
# 浅拷贝(copy/切片) 只拷贝第一层,修改 c[0]=99 不影响 a,但修改 c[2][0]=999 会同时改动 a[2]。
# 深拷贝(deepcopy) 完全递归复制,绝对安全。
a = [1, 2, [3, 4]]
b = a            	# 坑!b 和 a 指向同一对象,改 b 就是改 a
c = a.copy()     	# 浅拷贝:外层独立,但内层列表仍共享
d = a[:]         	# 也是浅拷贝
e = copy.deepcopy(a)# 深拷贝:完全独立,内外都不受影响

1.3 元组 tuple (,)

  • 元组是一种不可变 的、有序 的数据集合。使用小括号 () 定义,元素用逗号 , 隔开。
  • 核心对比(列表 vs 元组):
    • 列表 [ ]:可变(增删改),速度慢,用于动态数据。
    • 元组 ( ):不可变(只读),速度快,内存小,用于固定数据。
python 复制代码
# 创建元组的4种方式
	#1 标准小括号
    t1 = (1, 2, 3, '苹果', '香蕉')
    #2 只有一个元素时,必须加逗号!否则就是普通变量
    t3 = (5)     # 这是整数 5
    t4 = (5,)    # 这才是元组    
	#3 内置函数 tuple() 把其他类型转为元组
    t2 = tuple(['a', 'b', 'c'])    
	#4 省略括号(逗号是关键)
    t5 = 100, 200, 300


# 查找元素
my_tuple = ('P', 'y', 't', 'h', 'o', 'n')
# 正向&反向索引(从左到右,从 0 开始)
print(my_tuple[0])   # 'P'
print(my_tuple[-1])  # 'n' (最后一个)
# 切片 [起始 : 结束(不包含)]
print(my_tuple[1:4])   # ('y', 't', 'h')


my_tuple = (1, 3, 5, 3, 7, 3)
# count():统计某个元素出现次数
print(my_tuple.count(3))  # 3
# index():查找元素第一次出现的索引位置,如果不存在,报ValueError
print(my_tuple.index(7))  # 4



# 元组不能修改,不能增删改,只能查
my_tuple[0] = 100      # 不能改
my_tuple.append(40)    # 不能加
del my_tuple[1]        # 不能删
# 唯一允许的操作:删除整个元组变量(极少用)
del my_tuple  # 删掉后变量就不存在了


# 元组合并&重复
a = (1, 2);b = (3, 4)
c = a + b        # 合并
print(c)         # (1, 2, 3, 4)
print(a)         # (1, 2) ------ 原元组完好无损
d = a * 3        # 重复3次=(1, 2, 1, 2, 1, 2)


# 循环遍历元组
my_tuple = ('apple', 'banana', 'orange')
	# 方法1:直接取元素(最常用)
    for item in my_tuple:
        print(f"水果: {item}")       
	# 方法2:同时取下标和值(使用 enumerate)
    for index, value in enumerate(my_tuple):
        print(f"第 {index} 个是: {value}")
    
    
# 解包 Unpacking
	# 基础解包:变量个数 = 元组元素个数
    my_tuple = ('张三', 18, '男')
    name, age, gender = my_tuple
    print(name)   # 张三
    print(age)    # 18
    
	# 进阶:用 * 匹配剩余元素(多余元素变成列表)
    nums = (1, 2, 3, 4, 5)
    first, *middle, last = nums
    print(first)   # 1,int类型
    print(middle)  # [2, 3, 4] (注意是列表)
    print(last)    # 5,int类型


# 经典应用:交换两个变量(不用临时变量)
x = 10;y = 20
x, y = y, x   # 右边生成 (20, 10),再解包给左边
print(x,y)      # 20,10


# 元组"不可变"是指它保存的"引用(地址)"不变。
my_tuple = (1, 2, [3, 4])
my_tuple[2] = [5, 6]  	# 报错(替换了整个列表对象)
my_tuple[2][0] = 99     # 不报错
print(my_tuple)         # (1, 2, [99, 4])列表内容变了,但元组没变

1.4 集合 Set {""}

  • 集合是一个无序 的、元素不重复 的、可变的数据集合。
  • 使用花括号 {} 定义,元素用逗号 , 隔开。
  • 集合内永远不可能有重复元素,重复的会被自动合并。
  • 核心区别
    • 列表(List):有序、可重复、可变。
    • 元组(Tuple):有序、可重复、不可变
    • 集合(Set)无序不重复、可变。
  • 集合是无序 的。它基于哈希表存储,元素不重复,但不保证任何顺序,所以不能角标查询数值
python 复制代码
# 创建集合3种方式
	# 方式1:标准花括号(最常用)
    my_set = {1, 2, 3, 4}    
	# 方式2:内置函数 set() 把其他类型转为集合(自动去重!)
    my_set = set([1, 2, 2, 3, 3, 3])  # 列表转集合
    my_set = set('hello')  # 字符串转集合
    print(my_set)  # {'h', 'e', 'l', 'o'} ------ 注意:去重了(l只保留一个),且顺序是乱的    
	# 方式3:创建空集合(千万注意!)
    my_set = {}       # <class 'dict'>这是空字典(dict),不是空集合! 
    my_set = set()    # <class 'set'>这才是空集合的正确写法


# 集合内永远不可能有重复元素,重复的会被自动合并。
my_set = {1, 1, 2, 2, 3, 3, 3}
print(my_set)  # {1, 2, 3} ------ 重复的自动消失
	# 实际应用:快速去除列表中的重复项
    my_list = [5, 5, 6, 7, 7, 7]
    my_set = set(my_list)   # 转成集合去重


# 添加元素(Add & Update)
my_set = {1, 2}
	#1 add():添加单个元素(如果已存在,则什么都不做)
    my_set.add(3)	# my_set = {1, 2, 3}
    my_set.add(2) 	# 2已经存在,不会重复添加 my_set = {1, 2, 3}
    #2 update():批量添加(传入列表、元组、字符串等可迭代对象)
    my_set.update([4, 5, 6])     # 追加列表中的元素
    print(my_set)  # {1, 2, 3, 4, 5, 6}
    my_set.update('abc')         # 追加字符串中的单个字符
    print(my_set)  # {1, 2, 3, 4, 5, 6, 'a', 'b', 'c'}


# 删除元素(Remove / Discard / Pop / Clear)
my_set = {1, 2, 3, 4, 5}
	#1 remove():删除指定元素,如果不存在会报错 KeyError
    my_set.remove(3)	# my_set = {1, 2, 4, 5}
    my_set.remove(99)  	# 去掉注释会报错:KeyError   
	#2 discard():删除指定元素,如果不存在则什么都不做(推荐!更安全)
    my_set.discard(4)	# my_set = {1, 2, 5}
    my_set.discard(99)  # 不会报错,静默处理    
	#3 pop():随机删除并返回一个元素(因为集合无序,所以随机)
    random_item = my_set.pop()
    print(random_item)  # 可能是 1 或 2 或 5(随机)
    print(my_set)      	# 剩下的元素    
	#4 clear():清空集合
    my_set.clear()		# my_set = set()



# 遍历集合(Looping)集合也是可迭代的,但因为无序,每次运行输出的顺序可能不一样。
my_set = {'apple', 'banana', 'orange'}
	#1 直接遍历元素
    for item in my_set:
        print(f"水果: {item}")
    # 遍历时同时获取下标?做不到,因为集合没有顺序。
    # 如果确实需要下标,先转为列表:list(my_set)
    
    
# 🔥数学运算(交集、并集、差集),这是集合最大的价值所在,比列表处理快得多!
my_set_1 = {1, 2, 3, 4}
my_set_2 = {3, 4, 5, 6}
	#1 并集(Union):取两个集合的全部元素(去重)
        # 方式A:使用 | 符号
        result = my_set_1 | my_set_2		# result={1, 2, 3, 4, 5, 6}
        # 方式B:使用 union() 方法
        result = my_set_1.union(my_set_2)	# result={1, 2, 3, 4, 5, 6}
	#2 交集(Intersection):取两个集合共有的元素
        # 方式A:使用 & 符号
        result = my_set_1 & my_set_2				# result={3, 4}
        # 方式B:使用 intersection() 方法
        result = my_set_1.intersection(my_set_2)	# result={3, 4}
	#3 差集(Difference):在 my_set_1 中有但 my_set_2 中没有的
        # 方式A:使用 - 符号
        result = my_set_1 - my_set_2			# result={1,2}
        # 方式B:使用 difference() 方法
        result = my_set_1.difference(my_set_2)	# result={1,2}
    #4 对称差集(Symmetric Difference):不同时存在于两个集合中的元素(即并集减去交集)
        # 方式A:使用 ^ 符号
        result = my_set_1 ^ my_set_2		# resul={1, 2, 5, 6}
        # 方式B:使用 symmetric_difference() 方法
        result = my_set_1.symmetric_difference(my_set_2)	# resul={1, 2, 5, 6}
        
        
# 成员关系测试(in / not in),判断元素是否在集合中,速度极快(比列表快得多)。
my_set = {'张三', '李四', '王五'}
print('张三' in my_set)      # True
print('赵六' in my_set)      # False
print('赵六' not in my_set)  # True



# 子集与超集(Subset / Superset),判断一个集合是否包含于另一个集合。
A = {1, 2}
B = {1, 2, 3, 4}
# A 是否是 B 的子集(A 的所有元素都在 B 里)
print(A.issubset(B))  # True
# B 是否是 A 的超集(B 包含 A)
print(B.issuperset(A))  # True


# 不可变集合(frozenset)------了解即可
# 普通的集合是可变的(可以增删改)。如果需要一个不可变的集合(例如作为字典的键),使用 frozenset。
my_frozen = frozenset([1, 2, 3])
my_frozen.add(4)  # 会报错:AttributeError(不可变)
print(my_frozen)  # frozenset({1, 2, 3})

# 空集合写成 {} 变成字典
my_set = {}          # 这是 dict,不是 set!
print(type(my_set))  # <class 'dict'>
my_set = set()       # 这才是空集合

# 集合不能存储可变元素(如列表、字典)
my_set = {[1, 2], 3}	# 报错 TypeError(列表不可哈希)
my_set = {(1, 2), 3}    # 但可以存元组(元组不可变,可以)

# 集合没有顺序(千万别用下标取元素!)
my_set = {10, 20, 30}
print(my_set[0]) 	#报错 TypeError(集合不支持索引)
# 想取第一个?转成列表再取:list(my_set)[0](但不推荐,因为顺序是乱的)

1.5 字典 {"","}

  • 字典是 Python 中唯一一种 "映射(Mapping)" 类型。
  • 它存储的是 键值对(Key-Value Pairs)
  • 键(Key) :必须是不可变 类型(字符串、数字、元组),且唯一(就像身份证号)。
  • 值(Value):可以是任意类型(列表、数字、字符串、甚至另一个字典),可以重复。
  • 核心特性可变 (可以增删改),Python 3.7+ 开始保证插入顺序(记住这一点,面试常考)。
python 复制代码
# 创建新字典
	# 方式1:标准花括号(最常用)
	my_dict = {"name": "张三", "age": 18, "city": "北京"}
	# 方式2:使用 dict() 内置函数(从键值对列表创建)
	my_dict = dict([("name", "李四"), ("age", 20)])
	# 方式3:使用关键字参数(键名必须符合变量命名规则,不能是数字或带空格)
	my_dict = dict(name="王五", age=22)
	# 方式4:创建空字典(千万注意!别和集合搞混)
	my_dict = {}       # 这是空字典 
	my_set = set()     # 这是空集合,空集合必须用 set()


# 添加键值对
my_dict["key1"] = "value1"
# 批量更新:update()(传入另一个字典)
new_data = {"height": 175, "weight": 70}
my_dict.update(new_data)	# {'name': '张三', 'age': 19, 'city': '北京', 'height': 175, 'weight': 70}


# 删除键值对
	# 无返回值
	del my_dict["key1"]	# 无返回值,如果键不存在会报错
	# 有返回值
	val1 = my_dict.pop("key1")	# 返回删除的值
	val1 = my_dict.pop("key1", "默认返回值") # key1不存在,返回默认值
	# popitem():删除并返回最后插入的一个键值对(Python 3.7+ 有序)
	return_val = my_dict.popitem()	# (返回一个元组)
	# clear():清空字典
	my_dict.clear() # {}


# 修改键值对
my_dict["key1"] = "新的value"
my_dict.update({"key1":新的value})


# 获取指定键值对
str = my_dict["key1"]		# 键不存在,会报错 KeyError
str = my_dict.get("key1")	# 键不存在,显示None
str = my_dict.get("key1","获取失败的默认值")


# 遍历字典
for key, value in my_dict.items():
    print(f"{key} -> {value}")


# 获取keys与values
my_dict.keys()
my_dict.values()

1.6 常用函数

python 复制代码
# for循环
for a(变量) in list(数据源):
    操作变量a
# while循环
while num1>num2:
    print(num1)
# 循环终止 break,直接退出所有循环
# 循环跳过 continue,跳出当前循环,忽略剩下部分,继续新的循环。
    
# 创建数值列表
range(10)		# 输出0~9
range(首元素值,末元素值+1)	# range(2,5) = 2~4
range(首元素值,末元素值+1,步进数值)	# range(2,6,2) = 2,4

# 最大值,最小值,求和
max(list)
min(list)
sum(list)

# 数据类型转换
int(str)

# input输入
str = input("展示提示信息:")

1.7 函数&方法 def

python 复制代码
# 无参函数
def fun1():
    """我是文档字符串 & 函数注释"""
    print("Hello Function!")
fun1()	# 使用函数,打印字符串
------------------------------
# 有参函数
def fun1(userName):
    print("打印字符串userName"+userName)
fun1("张三")	# 调用函数


------------------------------
def fun1(str1,str2="我是默认参数"):
    print(str1+" "+str2)
    return str1
fun1("张三","李四")				# 位置实参,参数有顺序要求
fun1(str1="张三",str2="李四")	# 关键字实参,实参与形参对应


------------------------------
# 任意数量的形参,参数str1是元组,接收函数所有参数
def fun1(*str1):
fun1(11,22,33,44)			# 参数str1=(11,22,33,44),是个元组

def fun2(*str1,str2):		# 任意在前,实际在后
fun2(11,22,33,44,str2="55")	# 参数str1=(11,22,33,44),str2="55"

def fun3(str2,*str1):		# 实际在前,任意在后
fun3("11",22,33,44)			# 参数str2="11",str1=(22,33,44)

------------------------------
# 任意数量关键字参数,dir1是字典
def fun4(p1,p2,**dir1):
    dir1["str1"]=p1
    dir1["str2"]=p2
    return dir1 
fun4("111","222",str3="33",str4="44")
>>> {'str3': '33', 'str4': '44', 'str1': '111', 'str2': '222'}

1.8 模块 module

python 复制代码
# 创建module1.py
def module1_fun1():
    print("this is a module1_fun1")
    return "this is a module1_fun1"
# 导入模块并使用
import module1.py
module1.module1_fun1()
>>> "this is a module1_fun1"

------------------------------
# 导入全部模块
import module1.py
module1.module1_fun1()

# 导入模块指定函数
from module1 import module1_fun1
module1_fun1()
# 导入模块全部函数
from module1 import *


# 避免重复起别名
from module1 as m1		# 模块起别名
from module1 import module1_fun1 as new_fun1;	# 新名称new_fun1

1.9 类 class

python 复制代码
class Dog:		# 定义类
    """文档字符串,简要描述类的功能"""
    
    # __init__特殊方法-初始化方法,类创建会自动运行,self是形参(必须),str1,str2是普通参数
    def __init__(self,name,age):
        self.name=name;		# self定义的变量全类内可用
        self.age=age;
        self.colour="黄色"
    
    def fun1(self):		# 普通方法
        print("这是自定义方法")
    def change_colour(self):
        self.colour="黑色"
        
------------------------------
# 实例化对象
dog = Dog("小黄","22")
# 访问属性
dog.name # 小黄
# 修改属性
dog.colour="白色"			# 1 直接修改
dog.change_colour()		# 2 定义方法修改
python 复制代码
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print("this is Animal init")
    def fun1(self):
    	print("this is Animal fun1's message")

class Dog(Animal):
    def __init__(self, name, age):
        super().__init__(name, age)     # 初始化父类属性
        self.name = name
        self.age = age
        self.teeth = 100
        print("this is Dog init")
    def fun1(self):		# 重写父类fun1()
    	print("this is Dog fun1's message")
# 实例化对象
dog = Dog("Dog", 5)
animal = Animal("Animal", 50)
# 输出 Animal > Dog > Animal
  • 每个与实例相关联的方法调用都会自动传递实参 self,该实参是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
  • self参数仅使用于 类的方法参数中 ,普通函数不需要第一个self参数。

1.10 文件&文件夹 pathlib.Path

python 复制代码
# 读取文件
from pathlib import Path

# 创建文件
path = Path('file1.txt')

# 判断路径是存在
path.exists()		# True / False

# 程序到 目标文件 的相对地址
print(path.parent)

# 当前程序的绝对地址
str = Path(__file__).parent
# 目标文件的绝对地址E:\WorkSpace\PyCharm\Hello\test1\file_txt\1.txt
str = p.resolve()

print(path.name)        # 完整文件名
print(path.stem)        # 无后缀文件名
print(path.suffix)      # 文件类型后缀
print(path.parent)      # 文件所在目录(仅父目录)
print(path.anchor)      # 根目录,windows显示空白

print(path.exists())    	# True/False(是否存在)
print(path.is_file())   	# True/False(是否为文件)
print(path.is_dir())    	# True/False(是否为文件夹)
print(path.is_absolute()) 	# True/False(是否为绝对路径)

# 文件重命名
path.rename("new_name.txt")

# 获取文件大小(字节)
size1 = path.stat().st_size

# 获取最后修改时间
time1 = path.stat().st_mtime

----------------------------------------------
# 获取全部内容,包括换行符,很多行
contents = path.read_text(encoding='utf-8')
print(contents)
# 获取所有行(去除换行符)return列表
lines = contents.splitlines()	# ["","",""]
for line in lines:
	print(line)
python 复制代码
# 创建文件夹(parents=True 自动创建中间缺失的父目录,exist_ok=True 防止已存在报错)
new_dir = Path("data/2026/logs")
new_dir.mkdir(parents=True, exist_ok=True)

# 删除文件夹,必须文件夹,必须为空
dir = Path("./data/2026/1.py")
dir.rmdir()

folder = Path("my_project")
# 列举该目录下所有 .py 文件(非递归)
for f in folder.glob("*.py"):
    print(f)
# 递归查找所有 .txt 文件(**/* 表示任意子目录)
for f in folder.glob("**/*.txt"):
    print(f)
# 列举当前文件夹下所有内容
for item in folder.iterdir():
    print(item.name)
----------------------------------------------
# 写入文件
from pathlib import Path
path = Path('new_file1.txt')
# 自动创建文件,返回修改的数据量
### ⚠⚠⚠ write_text()方法会删除旧的内容 ⚠⚠⚠ ###
path.write_text("这是新的写入内容")

# 删除文件
if path.exists():
    path.unlink()

1.10.1 reader 迭代器(多类型通用)

  • 使用csv.reader而不是reader的原因是:reader是csv模块的功能,python没有默认集成,所以需要声明并调用csv.reader避免reader关键字同名。
python 复制代码
path = Path('./datas/sitka_weather_07-2021_simple.csv')
# read_text()按照地址读取所有内容
# splitlines()按照格式分割
list_str = path.read_text().splitlines()
# reader定义迭代器,但是未使用,无法获取数据
reader = csv.reader(list_str)
# 迭代器开始使用,有0号数据
print(next(reader))
# 从1号数据继续迭代,读取剩下所有行
print(list(reader))

1.10.2 path.open+reader改进,减少内存开销

python 复制代码
from pathlib import Path
import csv

path = Path('./datas/sitka_weather_07-2021_simple.csv')

# 关系建立:Path -> open() -> 文本流 -> csv.reader
# f 是惰性文件读取器
with path.open(encoding='utf-8') as f:   
    # reader 直接包装 f
    reader = csv.reader(f)               
    # 只读第一行(表头)
    header = next(reader)                

1.11 json数据获取与使用

1.11.1 核心知识点

  • Python 的 json 模块只有 4 个核心方法,记住 "有 s 是字符串,无 s 是文件" 就能一通百通:

  • Python没有json对象类型,本质是字典/列表。

  • 只有两种结构(互相套娃)

    • 对象 { } → Python 字典,存键值对。键必须用双引号。
    • 数组 [ ] → Python 列表,存有序的值。
  • 只有6种值

    • 字符串数字true/false(小写)null对象{}数组\[\]
  • 3条注意事项

    • 键必须用双引号"name" 对,name 错,'name' 错)

    • 不准有尾随逗号[1,2,] 错)

    • 不准写注释// 不行,/* */ 也不行)

方法 后缀有 s ? 作用 输入 输出
json.dumps() 有 s 序列化(写) Python 对象 字符串(str)
json.loads() 有 s 反序列化(读) 字符串(str) Python 对象
json.dump() ❌ 无 s 序列化(写) Python 对象 + 文件对象 直接写入文件
json.load() ❌ 无 s 反序列化(读) 文件对象 Python 对象
python 复制代码
from pathlib import Path    # 路径处理
import json                 # json处理

# json序列化,对象 -> 字符串
nums_list = [1,2,3,4,5,6,7,8,9,10]   # 源数据
path=Path("./my_json.json")
# dump=倾倒/丢弃,转储(计算机术语)
contents = json.dumps(nums_list)   
path.write_text(contents,encoding="utf-8")

# =============================================
# json反序列化,字符串 -> 对象
path=Path("./my_json.json")
contexts = path.read_text(encoding="utf-8")
nums_list = json.loads(contexts)		

1.11.2 GeoJSON

python 复制代码
# 文件内容处理
# 处理时间格式
# 文件读取
import json
from pathlib import Path

# 画图
import matplotlib.pyplot as plt
# 散点图
import plotly.express as px

# 指定使用支持中文的字体(黑体/微软雅黑等)# 2. 解决负号 '-' 显示为方块的问题(必须写)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC']
plt.rcParams['axes.unicode_minus'] = False
# 画布风格,必须在画布创建之前有效
plt.style.use('ggplot')  # 灰底橙线

# =============================================================

path = Path('eq_data/eq_data_1_day_m1.geojson')

# json_str -> json_obj
contents_json = path.read_text(encoding='utf-8')
json_obj = json.loads(contents_json)

# json_obj -> geojson_str
path = Path("eq_data/1.geojson")
# indent=缩进,美观
contents_geojson = json.dumps(json_obj, indent=4)
path.write_text(contents_geojson)

# =============================================================

path = Path("./eq_data/1.geojson")
contents_geojson = json.loads(path.read_text(encoding='utf-8'))

# 查看所有地震
all_eq = contents_geojson["features"]
# 查看地震等级
mags, titles, lons, lats = [], [], [], []
for eq_dict in all_eq:
    mag = eq_dict['properties']['mag']          # 地震等级
    title = eq_dict['properties']['title']      # 标题
    lon = eq_dict['geometry']['coordinates'][0] # 经度
    lat = eq_dict['geometry']['coordinates'][1] # 纬度
    mags.append(mag)
    titles.append(title)
    lons.append(lon)
    lats.append(lat)

# 地震散点图
fig = px.scatter(
    # 数据配置
    x=lons,
    y=lats,
    # 每个点显示的参数名称
    labels={'x': '经度', 'y': '纬度'},
    # 散点图坐标轴范围与长度
    range_x=[-200, 200],
    range_y=[-90, 90],
    width=800,
    height=800,
    title='全球地震散点图',
)
fig.write_html('global_earthquakes.html')
fig.show()

1.12 异常处理 traceback

python 复制代码
from pathlib import Path
try:
    print(1/0)						# 数据计算错误
    path1 = Path("./1111.txt")		# 文件路径错误
    contents = path1.read_text(encoding="utf-8")
    print(contents)
except ZeroDivisionError:	#except后面跟错误类型
    print("错误类型是数字除0")
except FileNotFoundError:	# 文件未找到 1111.txt
    print(f"文件未找到,路径={path1}")	
except ValueError 			# 传入无效参数
	pass					# 占位符,什么都不做,但是表示处理过了。
else:
    print("try块执行完后才会执行else块")

1.13 测试 pytest

  • windows系统安装:键盘快捷键winodws+r,输入cmd,在命令行输入" pip install pytest "即可
  • 测试时候直接运行无效果,必须打开终端,并切换到当前文件夹,输入pytest
    • " pytest " 默认运行同级别所有测试
    • " pytest 文件名 " 表示运行指定文件的测试
python 复制代码
# 单个函数测试
# 使用时要求类名字为"test_AAAA.py"或者"AAAA_test.py"
# 类内测试方法要求"test_AAAA"结构。
def fun1(str1,str2):
    """测试函数"""
    return str1+str2

def test_fun1():        # 测试方法必须test_打头
    str1 = fun1("111","222")
    assert str1 == "111222"    # assert断言,声明满足特定条件,供pytest判断 
python 复制代码
# 类测试
class test_calss1:	# 测试类,普通类
    """类的测试"""
    def __init__(self,str2):
        self.str2 = []
        self.str2.append(str2)
    def get_str2(self):
        return self.str2
    def add_str2(self,str2):
        self.str2.append(str2)
# ---------------------------------------------------
def test_for_class1():
    """测试上面的类"""
    class1 = test_calss1("111")
    class1.add_str2("222")
    list1 = ["222","333","444"]
    for i in list1:
        assert i in class1.get_str2()	# 使用assert运行测试
python 复制代码
# 使用夹具构建测试,需要导入pytest包
# 夹具默认每次测试都重新创建实例
import pytest		# 导入pytest包
class test_calss1:	# 测试类,普通类
    """类的测试"""
    def __init__(self,str2):
        self.str2 = []
        self.str2.append(str2)
    def get_str2(self):
        return self.str2
    def add_str2(self,str2):
        self.str2.append(str2)
# ---------------------------------------------------
@pytest.fixture						# 必须
def fun1():
    class1 = test_calss1("111")
    class1.add_str2("222")
    return class1
def test_fun2(fun1):               # 测试方法参数=装饰器方法的方法名
    assert "111" in fun1.get_str2()
def test_fun3(fun1):               # 测试方法参数=装饰器方法的方法名
    list1 = ["111","222","333"]
    for i in list1:
        assert i in fun1.get_str2()
# ---------------------------------------------------
# -----------------下面是模板2-------------------------
import pytest
class Employee:
    def __init__(self, name, sex, salary=0):
        self.name = name
        self.sex=sex
        self.salary=salary
    def give_raise(self,num=0):
        if num!=0:
            self.salary+=num
        return self.salary
# ---------------------------------------------------
@pytest.fixture
def fun1():
    return Employee("John", "Male")
def test_fun2(fun1):
    assert fun1.give_raise()==0			# 通过
def test_fun3(fun1):
    assert fun1.give_raise(5000)==5000	# 通过



2 Pygame 外星人入侵游戏

# 前期准备

python 复制代码
- 终端安装 Pygame 输入 " pip install pygame " 

2.1 常用方法

2.1.1 图像rect矩形属性,处理元素的位置

  • 核心定位属性
属性 类型 说明 示例 (假设 rect 在 (100, 100), 50x30)
x, y 整数 左上角的 x, y 坐标 x=100, y=100
left, top 整数 左边缘的 x 坐标,上边缘的 y 坐标 left=100, top=100
right, bottom 整数 右边缘的 x 坐标 (left + width),下边缘的 y 坐标 (top + height) right=150, bottom=130
centerx, centery 整数 中心点的 x, y 坐标 centerx=125, centery=115
center 元组 (x, y) 中心点的坐标 center=(125, 115)
width, height 整数 矩形的宽度和高度 width=50, height=30
size 元组 (w, h) 矩形的尺寸 size=(50, 30)
  • 对齐点属性
属性 类型 说明 示例 (假设 rect 在 (100, 100), 50x30)
topleft 元组 (x, y) 左上角坐标 topleft=(100, 100)
topright 元组 (x, y) 右上角坐标 topright=(150, 100)
bottomleft 元组 (x, y) 左下角坐标 bottomleft=(100, 130)
bottomright 元组 (x, y) 右下角坐标 bottomright=(150, 130)
midtop 元组 (x, y) 顶部中点坐标 midtop=(125, 100)
midleft 元组 (x, y) 左侧中点坐标 midleft=(100, 115)
midbottom 元组 (x, y) 底部中点坐标 midbottom=(125, 130)
midright 元组 (x, y) 右侧中点坐标 midright=(150, 115)

# alien.py

python 复制代码
import pygame
import random
from pygame.sprite import Sprite


class Alien(Sprite):
    def __init__(self, main_game):
        super().__init__()
        self.main_screen = main_game.screen
        self.main_setting = main_game.setting

        # 外星人图像
        self.image = pygame.image.load("images/alien.bmp")
        self.rect = self.image.get_rect()

        # 外星人从屏幕左上角出现
        # self.rect.x = self.rect.width
        # self.rect.y = self.rect.height
        self.rect.x = 5
        self.rect.y = 5

        # 出生点位置
        self.x = float(self.rect.x)  # 非随机
        # self.rect.x = float(random.randint(0,self.main_screen.width)) # 随机

    def update(self):
        # 假设 self.x 是浮点数,用于精确移动
        # self.x += self.main_setting.alien_speed_x
        self.x += self.main_setting.alien_speed_x * self.main_setting.alien_speed_move_direction    # speed 可以是小数,如 0.5
        self.rect.x = int(self.x)  # 最后赋值给 rect 进行绘制
    #
    def check_edges(self):
        """判断是否超出边界,越界=True"""
        screen_rect = self.main_screen.get_rect()
        return (self.rect.right >= screen_rect.right) or (self.rect.left <= 0)

# bullet.py

python 复制代码
import pygame
from pygame.sprite import Sprite


class Bullet(Sprite):  # 继承Sprite精灵类,批量管理
    def __init__(self, main_game):  # 参数=主界面
        """创建子弹"""
        super().__init__()  # 精灵子类初始化
        self.main_screen = main_game.screen
        self.main_settings = main_game.setting
        self.color = self.main_settings.bullet_color
        # 创建子弹属性rect,因为子弹不是图像,所以必须用pygame.Rect()从头创建矩形
        # 参数(创建横,纵坐标,宽度,高度)
        self.rect = pygame.Rect(0, 0, self.main_settings.bullet_width, self.main_settings.bullet_height)
        # 设置子弹的位置,飞船顶部发射子弹
        self.rect.midtop = main_game.ship.rect.midtop
        # 存储用浮点数表示的子弹位置
        self.y_num = float(self.rect.y)

    # 更新子弹位置,默认方法名,可以被精灵自动管理
    def update(self):
        # 向上移动子弹,同时考虑子弹速度
        # rect.y属性是整数,为了避免误差,用float承接一下
        self.y_num -= float(self.main_settings.bullet_speed)
        self.rect.y = int(self.y_num)
        # 防止oom
        if self.rect.bottom < -10:
            self.kill()


    # 绘制子弹图像
    def draw_bullet_image(self):
        pygame.draw.rect(self.main_screen, self.color, self.rect)

# button.py

python 复制代码
import pygame


class Button:
    """开始按钮"""


    def __init__(self, main_game, msg):
        # 初始化按钮
        self.game_screen = main_game.screen
        self.game_screen_rect = self.game_screen.get_rect()
        # 设置按钮属性
        self.width, self.height = 200, 50
        self.button_color = (0, 135, 0)
        self.text_color = (255, 255, 255)
        # 默认字体,字号48
        self.font = pygame.font.SysFont(None, 48)
        # 创建按钮rect对象,并且居中
        self.rect = pygame.Rect(0, 0, self.width, self.height)  # 新建按钮
        self.rect.center = self.game_screen_rect.center
        # 展示按钮
        self._show_message(msg)

    def _show_message(self, msg):
        # font.render文字->图像,P1=目标信息,P2=反锯齿功能,P3文字颜色,P4背景色
        self.msg_button = self.font.render(msg, True, self.text_color, self.button_color)
        self.msg_button_rect = self.msg_button.get_rect()
        self.msg_button_rect.center = self.rect.center

    def _draw_button(self):
        # 主屏幕填充一个矩形,P1背景色,P2区域位置与大小
        self.game_screen.fill(self.button_color, self.rect)
        # blit绘制图像,P2绘制的位置
        self.game_screen.blit(self.msg_button, self.msg_button_rect)

# game.py

python 复制代码
import sys
import time

import pygame

from alien import Alien
from bullet import Bullet
from button import Button
from game_stats import GameStats
from scoreboard import Scoreboard
# 下面是自定义模块导入
from setting import Setting  # 从隔壁setting.py文件导入参数类
from ship import Ship


class AlienInvasionGame:
    """    管理游戏资源与行为    """

    def __init__(self):
        # 初始化游戏,并创建游戏资源,背景
        pygame.init()
        # 初始化参数类
        self.setting = Setting()
        # 创建游戏主窗口,参数=长,宽
        # self.screen 的对象是一个 surface(画布/图层),每个元素都需要一个surface。
        # self.screen = pygame.display.set_mode((self.setting.screen_width, self.setting.screen_height))  # 游戏固定大小
        self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)  # 游戏全屏
        # 窗口名称
        pygame.display.set_caption(self.setting.game_name)
        # 定义时钟,控制帧率
        self.clock = pygame.time.Clock()
        # 背景色
        self.bg_color1 = pygame.Color(self.setting.background_color1)  # 两种调色方式 1
        self.bg_color2 = self.setting.background_color2  # 两种调色方式 2
        # 背景图
        self.background = pygame.image.load(self.setting.background_image_name)

        # 创建飞船
        self.ship = Ship(self)  # 这里传入了game类实例作为参数,不是没有参数

        # 创建精灵列表,方便统一管理
        self.bullets = pygame.sprite.Group()
        self.aliens = pygame.sprite.Group()

        # 生成敌人
        self.create_so_many_aliens_by_many_line()

        # 创建得分
        self.stats = GameStats(self)
        # 创建记分牌
        self.scoreboard = Scoreboard(self)

        # 加载游戏状态
        self.game_activity = self.setting.game_activity
        # 创建开始按钮
        self.play_button = Button(self, "Play")

    def _check_events(self):
        """事务管理"""
        # 不断获取操作事件  pygame.event=游戏事件
        for event in pygame.event.get():
            # self._check_events()
            if event.type == pygame.QUIT:  # 退出类型 =256
                sys.exit()
            elif event.type == pygame.KEYDOWN:  # 键盘按下
                # print(f"进入 _check_key_down,键码: {event.key}")
                self._check_key_down(event)
            elif event.type == pygame.KEYUP:  # 键盘抬起
                self._check_key_up(event)
            elif event.type == pygame.MOUSEBUTTONDOWN:  # 触发鼠标点击事件
                # 鼠标点击时候的x,y坐标
                mouse_position = pygame.mouse.get_pos()
                self._click_play_button(mouse_position)

    def _check_key_down(self, event):
        """键盘按下"""
        # print(f"键盘按下,: {event.key}")
        if event.key == pygame.K_RIGHT:  # K_RIGHT=右键
            self.ship.moving_right = True
        elif event.key == pygame.K_LEFT:  # 左键
            self.ship.moving_left = True
        elif event.key == pygame.K_UP:  # 上键
            self.ship.moving_up = True
        elif event.key == pygame.K_DOWN:  # 下健
            self.ship.moving_down = True
        elif event.key == pygame.K_SPACE:  # 空格
            self.fire_bullet()
        elif event.key == pygame.K_q or pygame.KSCAN_ESCAPE:  # Q键
            sys.exit()

    def _check_key_up(self, event):
        """键盘抬起"""
        # print(f"键盘抬起: {event.key}")
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = False
        elif event.key == pygame.K_LEFT:
            self.ship.moving_left = False
        elif event.key == pygame.K_UP:
            self.ship.moving_up = False
        elif event.key == pygame.K_DOWN:
            self.ship.moving_down = False

    def fire_bullet(self):
        """子弹开火"""
        if self.setting.bullet_have_max_num and self.setting.bullet_max_num > len(self.bullets):
            new_bullet = Bullet(self)
            self.bullets.add(new_bullet)

    def create_alien_one(self, current_aliens_widths, current_aliens_heights):
        """生成一个敌人"""
        new_alien = Alien(self)
        # 真实位置
        new_alien.x = current_aliens_widths
        # 显示位置
        new_alien.rect.x = current_aliens_widths
        new_alien.rect.y = current_aliens_heights
        self.aliens.add(new_alien)

    def create_so_many_aliens_by_one_line(self):
        """demo样式生成舰队敌人"""
        alien = Alien(self)
        alien_width = alien.rect.width
        # 当前所有敌人宽度,包括间隔
        current_aliens_widths = alien_width
        # print(f"敌人总宽度={current_aliens_widths},屏幕宽度={self.screen.get_width()}")
        while current_aliens_widths < self.screen.get_width() - 2 * alien_width:
            self.create_alien_one(current_aliens_widths)
            current_aliens_widths += 2 * alien_width
            # print(f"新敌人的 x={new_alien.x}  x坐标={new_alien.rect.x}  敌人总宽度={current_aliens_widths}")

    def create_so_many_aliens_by_many_line(self):
        alien = Alien(self)
        # 外星人宽度与高度
        alien_width, alien_height = alien.rect.size

        current_aliens_widths, current_aliens_heights = alien_width, alien_height
        while current_aliens_heights < self.screen.get_height() - 3 * alien_height:
            # print(f"当前高度={current_aliens_heights}  总高度={self.screen.get_height()}")
            while current_aliens_widths < self.screen.get_width() - 2 * alien_width:
                self.create_alien_one(current_aliens_widths, current_aliens_heights)
                current_aliens_widths += 2 * alien_width
            # print(f"新敌人的 x={new_alien.x}  x坐标={new_alien.rect.x}  敌人总宽度={current_aliens_widths}")
            # 重置current_aliens_widths属性
            current_aliens_widths = alien_width
            current_aliens_heights += 3 * alien_height

    def _update_aliens(self):
        self.check_aliens_fleet_game_edge()
        self.aliens.update()

        # 检测飞船与子弹碰撞
        if pygame.sprite.spritecollideany(self.ship, self.aliens):
            print("你被击中了")
            self._ship_hit()
        # 检测敌人有没有到最下面
        self._check_alien_arrive_bottom()

    def check_aliens_fleet_game_edge(self):
        """检查外星人是否到达游戏边界"""
        for alien in self.aliens.sprites():
            if alien.check_edges():
                self._change_fleet_height()
                break

    def _change_fleet_height(self):
        """外星人到边界了,开始换行"""
        for alien in self.aliens.sprites():
            alien.rect.y += self.setting.alien_speed_y  # 整体移动
        self.setting.alien_speed_move_direction *= -1

    def _update_bullets(self):
        """更新子弹"""
        self.bullets.update()  # 更新子弹编组,会自动使用每个编组成员的update方法

        # 消除超出边界的子弹
        for bullet in self.bullets.sprites():
            if bullet.rect.bottom < 0:
                self.bullets.remove(bullet)
        self._check_bullet_love_alien_collections()

    def _check_bullet_love_alien_collections(self):
        """子弹爱上了外星人"""
        # sprite.groupcollide() 检测两个编组的成员之间的碰撞
        aim_collections = pygame.sprite.groupcollide(self.bullets, self.aliens, True, True)
        # 命中得分
        if aim_collections: # 字典类型,返回碰撞的(子弹,敌人)
            for aliens in aim_collections.values():
                self.stats.score += self.setting.alien_points * len(aliens)
            self.scoreboard.prep_score()
            self.scoreboard.check_max_score()
        # 没有敌人就开始新的敌人
        if not self.aliens:
            self.bullets.empty()  # 清空子弹
            self.create_so_many_aliens_by_many_line()  # 新的敌人
            # 增加游戏难度
            self.setting.increase_speed()
            # 提高游戏等级
            self.stats.level += 1
            self.scoreboard.prep_level()

    def _click_play_button(self, mouse_position):
        """点击开始游戏"""
        print(f"已点击 '游戏开始按钮' ")
        button_click = self.play_button.rect.collidepoint(mouse_position)
        if button_click and not self.game_activity:
            # 重置游戏信息
            self.stats.reset_stats()
            self.game_activity = True

            # 重置游戏难度
            self.setting.initialize_dynamic_settings()
            # 重置游戏得分表
            self.scoreboard.prep_score()
            # 重置游戏等级
            self.scoreboard.prep_level()
            # 重置游戏生命值
            self.scoreboard.prep_ships()

            # 清空外星人列表和子弹列表
            self.bullets.empty()
            self.aliens.empty()

            # 创建一个新的外星舰队,并将飞船放在屏幕底部的中央
            self.create_so_many_aliens_by_many_line()  # 重复了感觉,没这行也能直接游戏
            self.ship.center_ship()
            # 设置光标是否可见
            pygame.mouse.set_visible(False)

    def _update_screen(self):
        """更新屏幕"""
        # 屏幕背景填色
        self.screen.fill(self.bg_color2)

        # 展示背景图
        # self.screen.blit(self.background, (0, 0))   # 从指定位置开始画图

        # 展示飞船画面
        self.ship.show_ship()

        # 绘制子弹
        for bullet in self.bullets.sprites():
            bullet.draw_bullet_image()
        # 绘制外星人
        self.aliens.draw(self.screen)

        # 展示游戏得分
        self.scoreboard.show_score()

        # 判断开始状态,展示游戏开始按钮
        if not self.game_activity:
            self.play_button._draw_button()

        # 背后重绘的画面修改完成,更新屏幕画面,让背后的重绘屏幕展示出来
        pygame.display.flip()

    def run_game(self):
        """游戏运行主循环"""
        while True:
            self._check_events()  # 自定义事务管理方法

            # 判断飞船生命值HP
            if self.game_activity:
                self.ship.update_ship_rect()  # 根据飞船方向属性移动
                self._update_aliens()  # 更新外星人位置
                self._update_bullets()  # 更新子弹

            self.clock.tick(60)  # 时钟计时参数=游戏帧率
            self._update_screen()  # 自定义更新屏幕

    def _ship_hit(self):
        print(f"############ hp={self.stats.ship_hp} ")
        if self.stats.ship_hp > 1:
            # hp-1
            self.stats.ship_hp -= 1
            # 清空敌人与子弹
            self.bullets.empty()
            self.aliens.empty()
            # 创建一个新的外星舰队,并将飞船放在屏幕底部的中央
            self.create_so_many_aliens_by_many_line()
            self.ship.center_ship()
            # 刷新生命值
            self.scoreboard.prep_ships()
            # 暂停
            time.sleep(1)
        else:
            self.game_activity = False
            # 设置光标是否可见
            pygame.mouse.set_visible(True)

    def _check_alien_arrive_bottom(self):
        for alien in self.aliens.sprites():
            if alien.rect.bottom >= self.screen.get_height():
                # 外星人跑到最下面了,飞船hp-1
                self._ship_hit()
                break


# 检查当前脚本是不是被直接运行的,直接运行=True
if __name__ == '__main__':
    ai = AlienInvasionGame()  # 创建游戏实例
    ai.run_game()  # 启动游戏

# game_stats.py

python 复制代码
class GameStats:
    """跟踪游戏统计信息"""

    def __init__(self, game):
        """统计初始化信息"""
        self.game_settings = game.setting
        self.reset_stats()
        # 游戏最高分数
        self.max_score = 0
        # 游戏等级
        self.level = 1

    # 重置生命值与游戏得分
    def reset_stats(self):
        """初始化生命值,游戏得分"""
        self.ship_hp = self.game_settings.ship_hp
        self.score = 0

# scoreboard.py

python 复制代码
import pygame
# 导入飞船与外星人编组
from pygame.sprite import Group

from ship import Ship


class Scoreboard:
    """ 显示得分"""

    def __init__(self, main_game):
        # 初始化游戏生命值
        self.main_game = main_game

        """初始化得分"""
        self.game_screen = main_game.screen
        self.game_screen_rect = self.game_screen.get_rect()
        self.game_setting = main_game.setting
        # 状态
        self.game_stats = main_game.stats

        # 字体
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        # 准备初始得分图像
        self.prep_score()
        # 准备历史得分最大值
        self.prep_history_max_score()
        # 准备游戏等级显示
        self.prep_level()
        # 准备显示游戏生命值
        self.prep_ships()

    def prep_score(self):
        """将得分渲染为图像"""
        # 游戏得分处理
        num1 = round(self.game_stats.score, -1)  # 精确到小数点某一位
        # 获取得分
        score_str = f"{num1}"
        self.score_image = self.font.render(score_str, True, self.game_setting.board_text_color,
                                            self.game_setting.board_background_color)
        # 在屏幕右上角显示得分
        self.score_rect = self.score_image.get_rect()
        # 调整得分显示的位置
        self.score_rect.right = self.game_screen_rect.right - 20
        self.score_rect.top = 20

    def prep_history_max_score(self):
        max_score = str(round(self.game_stats.max_score))
        self.max_score_image = self.font.render(max_score, True, self.game_setting.board_text_color,
                                                self.game_setting.board_background_color)
        # 在屏幕右上角显示得分
        self.max_score_rect = self.max_score_image.get_rect()
        self.max_score_rect.center = self.game_screen_rect.center
        self.max_score_rect.top = self.game_screen_rect.top

    def show_score(self):
        """在屏幕上显示得分"""
        self.game_screen.blit(self.score_image, self.score_rect)
        # 展示游戏最高分
        self.game_screen.blit(self.max_score_image, self.max_score_rect)
        # 展示游戏等级
        self.game_screen.blit(self.level_image, self.level_rect)
        # 展示游戏生命值
        self.ships.draw(self.game_screen)

    def check_max_score(self):
        if (self.game_stats.score > self.game_stats.max_score):
            self.game_stats.max_score = self.game_stats.score
            self.prep_history_max_score()

    def prep_level(self):
        """将等级渲染为图像"""
        level_str = str(self.game_stats.level)
        self.level_image = self.font.render(level_str, True, self.game_setting.board_text_color,
                                            self.game_setting.board_background_color)
        # 将等级放在得分下方
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.top = self.score_rect.bottom + 10

    def prep_ships(self):
        self.ships = Group()
        for ship_number in range(self.game_stats.ship_hp):
            ship = Ship(self.main_game)
            ship.rect.x = 10 + ship_number * ship.rect.width
            ship.rect.y = 10
            self.ships.add(ship)

# setting.py

python 复制代码
class Setting:
    """游戏设置类"""

    def __init__(self):
        # 屏幕高宽
        # self.screen_width = 1800
        # self.screen_height = 600
        # 窗口名字
        self.game_name = "Alien Invasion Game 2026"
        # 背景色
        self.background_color1 = "Blue"
        self.background_color2 = (230, 230, 230)
        self.background_image_name = "images/kehuan.jpg"

        # 飞船属性
        self.ship_speed = 5
        self.ship_hp = 3

        # 子弹设置
        self.bullet_speed = 8.0
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (60, 60, 60)
        # 是否限制子弹数量
        self.bullet_have_max_num = True
        self.bullet_max_num = 100

        # 外星人属性
        self.alien_max_num = 30  # 人数上限
        self.alien_hang_num = 3
        self.alien_speed_x = 5
        self.alien_speed_y = 2
        # 外星人得分
        self.alien_points = 50
        self.alien_score_speed = 1.5

        # 移动方向
        self.alien_speed_move_direction = 1  # 1-> , -1<- , 0不移动

        # 默认游戏状态
        self.game_activity = False

        self.board_text_color = "red"
        self.board_background_color = "white"

        # 游戏额外加速
        self.game_speedup = 1.1
        self.game_level = 1
        # 初始化游戏难度
        self.initialize_dynamic_settings()

    # 初始化动态设置
    def initialize_dynamic_settings(self):
        print("初始化游戏速度")
        self.ship_speed = 5
        self.bullet_speed = 2.5
        self.alien_speed_x = 5
        self.alien_speed_y = 2
        # alien_speed_move_direction 为 1 表示向右,为-1 表示向左
        self.alien_speed_move_direction = 1
        self.game_level = 1
        # 重置游戏初始得分
        self.alien_points = 50

    def increase_speed(self):
        """提高速度设置的值"""
        self.game_level += 1
        print(f"游戏速度提升,当前等级 = {self.game_level}  {self.ship_speed}")
        self.ship_speed *= self.game_speedup
        self.bullet_speed *= self.game_speedup
        self.alien_speed_x *= self.game_speedup
        self.alien_speed_y *= self.game_speedup
        # 提到不同难度的得分
        self.alien_points = int(self.alien_points * self.alien_score_speed)

# ship.py

python 复制代码
import pygame
from pygame.sprite import Sprite


class Ship(Sprite):
    """飞船类  """

    def __init__(self, game):
        """
        初始化
        rect是Rectangle矩形的缩写,所有游戏元素都是矩形rect对象
        """
        # 精灵初始化
        super().__init__()

        # 主窗口的 screen保存。知道该往哪里绘制
        self.game_screen = game.screen
        # get_rect() 会返回一个矩形对象(Rect),它记录了 screen 的位置和大小
        self.game_screen_rect = self.game_screen.get_rect()

        # 加载飞船图像
        self.image = pygame.image.load("./images/ship.bmp")
        # get_rect()获取位置,大小
        self.rect = self.image.get_rect()
        # (X坐标,Y坐标,宽,高)飞船初始位置信息 = Rect(0, 0, 60, 48)
        print(f"飞船初始位置信息 = {str(self.rect)}")

        #  加载飞船的属性
        self.ship_setting = game.setting
        self.ship_speed = 1 * self.ship_setting.ship_speed  # 默认速度1*系数

        # 新飞船初始化位置
        # midbottom 是指矩形底部中间点的坐标。
        # self.screen_rect.midbottom = 屏幕底部中间点的坐标。
        self.rect.midbottom = self.game_screen_rect.midbottom
        # (X坐标,Y坐标,宽,高)飞船最终位置信息 = Rect(370, 552, 60, 48)
        print(f"飞船最终位置信息 = {str(self.rect)}")

        # 设置常量,表示飞船是否一直移动
        self.moving_right = False
        self.moving_left = False
        self.moving_up = False
        self.moving_down = False

    def update_ship_rect(self):
        """更新飞船位置"""
        if (self.moving_up and self.rect.top > 0):
            self.rect.y -= self.ship_speed
        if (self.moving_right and self.rect.right < self.game_screen_rect.right):
            self.rect.x += self.ship_speed
        if (self.moving_left and self.rect.left > 0):
            self.rect.x -= self.ship_speed
        if (self.moving_down and self.rect.bottom < self.game_screen_rect.bottom):
            self.rect.y += self.ship_speed

    def show_ship(self):
        """指定位置绘制飞船"""
        # blit()绘制方法,一个图会绘制到另一个图上
        self.game_screen.blit(self.image, self.rect)

    def center_ship(self):
        """将飞船放在屏幕底部的中央"""
        self.rect.midbottom = self.game_screen_rect.midbottom
        self.x = float(self.rect.x)



3 Matplotlib 静态图

  • 安装 " pip install matplotlib "

3.1 折线图

3.1.1 单数据折线图

python 复制代码
# 导入pyplot绘图函数
import matplotlib.pyplot as plt

# =========================================================================
# 指定使用支持中文的字体(黑体/微软雅黑等)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC']
# 2. 解决负号 '-' 显示为方块的问题(必须写)
plt.rcParams['axes.unicode_minus'] = False
# =========================================================================


# 数据源
x_value = [1, 2, 3, 4, 5]
y_value = [1, 4, 9, 16, 25]

# 画布风格,必须在画布创建之前有效
# plt.style.use('seaborn-v0_8')       # 淡蓝底蓝线条
plt.style.use('ggplot')  # 灰底橙线
# plt.style.use('fivethirtyeight')  # 灰底蓝线

# =========================================================================
# figure是画布,axes是绘图区(XY轴)
figure, axes = plt.subplots()

# plot(展示的数据) = 画折线图
axes.plot(x_value, y_value, linewidth=2)

# 图片标题,坐标轴标题
axes.set_title('图片标题', fontsize=24)
axes.set_xlabel("x轴标题", fontsize=14)
axes.set_ylabel("y轴标题", fontsize=14)
# 坐标轴文字大小,both表示xy都设置
axes.tick_params(axis='both', labelsize=10)

# 画好的图片展示出来
plt.show()

3.2.2 双数据折线图

python 复制代码
# 文件内容处理
import csv
# 处理时间格式
from datetime import datetime
# 文件读取
from pathlib import Path

# 画图
import matplotlib.pyplot as plt

# 指定使用支持中文的字体(黑体/微软雅黑等)# 2. 解决负号 '-' 显示为方块的问题(必须写)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC']
plt.rcParams['axes.unicode_minus'] = False
# 画布风格,必须在画布创建之前有效
plt.style.use('ggplot')  # 灰底橙线

# =============================================================

path = Path('./datas/sitka_weather_2021_simple.csv')
# 获取内容列表
lines = path.read_text().splitlines()
# 生成迭代器
reader = csv.reader(lines)
# 读取文件头,并且打印
csv_head = next(reader)
for index, str in enumerate(csv_head):
    print(f"{index}  {str}")

# =============================================================

# 高温列表 & 日期列表
highs_list, dates_list, lows_list = [], [], []
# 遍历当前迭代器(从第二行开始,逐行处理)
# 处理数据错误问题
for row in reader:
    try:
        high = int(row[4])
        low = int(row[5])
        datetime_1 = datetime.strptime(row[2], '%Y-%m-%d')
    except ValueError:
        print(f"缺失数据={datetime_1}")
    else:
        highs_list.append(float(row[4]))
        lows_list.append(float(row[5]))
        dates_list.append(datetime_1)

# =============================================================

# 创建画布
fig, ax = plt.subplots()
# 绘制温度折线图(日期为x轴,温度为y轴)
ax.plot(dates_list, highs_list, color='red', linewidth=1)
ax.plot(dates_list, lows_list, color='blue', linewidth=1)
# 图形范围 alpha=透明度
ax.fill_between(dates_list, highs_list, lows_list, facecolor='orange', alpha=0.3)

# =============================================================

# 设置绘图的格式
ax.set_title("我是标题", fontsize=20)
ax.set_xlabel('X标题', fontsize=16)
ax.set_ylabel("Y标题", fontsize=16)
# 自动旋转日期标签,避免重叠
fig.autofmt_xdate()
# 设置坐标轴刻度字体大小
ax.tick_params(labelsize=10)
plt.show()

3.2 散点图

3.2.1 pyplot简单散点图

python 复制代码
import matplotlib.pyplot as plt

# 指定使用支持中文的字体(黑体/微软雅黑等)# 2. 解决负号 '-' 显示为方块的问题(必须写)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC']
plt.rcParams['axes.unicode_minus'] = False
# 画布风格,必须在画布创建之前有效
plt.style.use('ggplot')  # 灰底橙线

# =========================================================================
# figure是画布,axes是绘图区(XY轴)
figure, axes = plt.subplots()

# 绘制数据点
axes.scatter(2,4)
# axes.scatter(range(10),range(10))     # range(10) = [0~9]
x_values = range(5)
y_values = [1, 4, 9, 16, 25]
# c=color,marker=标记样式,s=点的大小
axes.scatter(x_values, y_values, c='r', marker='x', s=10)



axes.set_title("散点图")
axes.set_xlabel("x轴标题",fontsize=20)
axes.set_ylabel("y轴标题",fontsize=20)
# 坐标轴数字大小
axes.tick_params(labelsize=10)
# 画好的图片展示出来
plt.show()

3.2.2 pyplot详细散点图

python 复制代码
import matplotlib.pyplot as plt

# =========================================================================
# 打印支持的颜色主题方案
print(plt.style.available)
plt.style.use('seaborn-v0_8-colorblind')
figure, axes = plt.subplots()

x_values = range(100)
y_values = [i ** 2 for i in range(100)]  # 使用列表推导式

# 设置x轴最小值~最大值  y轴最小值~最大值
axes.axis([0, 100, 0, 10_500])
# 不使用科学计数法表示
axes.ticklabel_format(style='plain')
# 数据点颜色


# 数据点普通颜色:c=color,marker=点形状,s=size
# axes.scatter(x_values, y_values, c='r', marker='x', s=10)

# 数据点颜色映射:渐变颜色
axes.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues, s=10)

# 保存绘图(名称,切除空白区域)
plt.savefig('test.png', bbox_inches='tight')
# 先保存,后展示
plt.show()

3.2.3 plotly简单散点图

  • 高度封装,自动创建画布,坐标轴,刻度,添加数据。
python 复制代码
import plotly.express as px
mags, titles, lons, lats = [], [], [], []
=========================================

# 地震散点图
fig = px.scatter(
    # 数据配置方式1
    x=lons,  # x轴数据源
    y=lats,  # y轴数据源
    labels={'x': '经度', 'y': '纬度'},  # 每个点显示的参数名称
    
    # 散点图坐标轴范围与长度
    range_x=[-200, 200],
    range_y=[-90, 90],
    width=800,
    height=800,
    title='全球地震散点图',
    size='震级',
    size_max=10
)
fig.write_html('global_earthquakes.html')
fig.show()

3.2.4 plotly复杂散点图

python 复制代码
import json
from pathlib import Path

import pandas as pd
import plotly.express as px  # 散点图

# =============================================================

path = Path('eq_data/eq_data_30_day_m1.geojson')
contents_geojson = json.loads(path.read_text(encoding='utf-8'))

# 查看所有地震
all_eq = contents_geojson["features"]
# 查看地震等级
mags, titles, lons, lats = [], [], [], []
for eq_dict in all_eq:
    mag = eq_dict['properties']['mag']  # 地震等级
    title = eq_dict['properties']['title']  # 标题
    lon = eq_dict['geometry']['coordinates'][0]  # 经度
    lat = eq_dict['geometry']['coordinates'][1]  # 纬度
    mags.append(mag)
    titles.append(title)
    lons.append(lon)
    lats.append(lat)

# 数据源2配置
data = pd.DataFrame(
    data=zip(lons, lats, titles, mags),
    columns=['经度', '纬度', '位置', '震级'])
data.head()

# 地震散点图
fig = px.scatter(
    # 数据配置方式2
    data,
    x='经度',
    y='纬度',

    # 散点图坐标轴范围与长度
    range_x=[-200, 200],
    range_y=[-90, 90],
    width=800,
    height=800,
    title='全球地震散点图',
    # 图标大小
    size='震级',
    size_max=10,
    # 多彩颜色
    color='震级',
    hover_name='位置',
)

fig.write_html('global_earthquakes.html')
fig.show()

3.3 随机游走

3.3.1 普通随机游走

​ 随机游走是由一系列简单的随机决策产生的行走路径。可以将随机游走看作一只晕头转向的蚂蚁每一步都沿随机的方向前行所经过的路径。

python 复制代码
random_walk_palin.py
============================================
import matplotlib.pyplot as plt
from random_walk import RandomWalk

rw = RandomWalk(5000)
rw.walk()

# 画图
plt.style.use('classic')
fig,ax=plt.subplots()
ax.scatter(rw.x_value,rw.y_value,c='blue',s=3)
# xy轴间距要求相同
ax.set_aspect('equal')

plt.show()
python 复制代码
random_walk.py
==========================================
from random import choice

class RandomWalk:
    """一个生成随机游走数据的类"""
    def __init__(self, num=5000):
        self.num = num
        # 随机游走的初始位置
        self.x_value = [0]
        self.y_value = [0]

    # 将所有可能的选择都存储在一个列表中,
    # 并在每次决策时使用 random 模块中的 choice() 来决定做出哪种选择
    def walk(self):
        while len(self.x_value) <= self.num:
            # 开始随机移动 direction=方向
            x_direction = choice([-1, 1])
            x_distance = choice([1,2,3,4])
            x_step = x_direction * x_distance

            y_direction = choice([-1, 1])
            y_distance = choice([1,2,3,4])
            y_step = y_direction * y_distance

            # 拒绝原地踏步
            if x_step == 0 and y_step == 0:
                continue

            # 计算坐标
            x=self.x_value[-1] + x_step
            y=self.y_value[-1] + y_step
            self.x_value.append(x)
            self.y_value.append(y)

3.3.2 复杂随机游走

python 复制代码
# 在3.3.1的基础上,仅修改random_walk_plain.py文件
==============================================
import matplotlib.pyplot as plt

from random_walk import RandomWalk

rw = RandomWalk(50000)
rw.walk()

# 画图
plt.style.use('classic')
# 普通方式创建画布
# fig, ax = plt.subplots()
# 屏幕尺寸与分辨率
fig, ax = plt.subplots(figsize=(10, 9), dpi=100)


# 可见数值长度 = 数据点个数
numbers = range(rw.num)

# 绘制随机游走点
ax.scatter(rw.x_value, rw.y_value, c=numbers, cmap=plt.cm.Blues, edgecolors='none', s=1)
# 绘制初始与末尾点
ax.scatter(0, 0, c='green', edgecolors='none', s=10)
ax.scatter(rw.x_value[-1], rw.y_value[-1], c='red', edgecolors='none', s=10)

# 隐藏坐标轴
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)


# xy轴间距必须相同
ax.set_aspect('equal')

plt.show()



4 Plotly 动态图

安装:pip install plotly pandas

4.1 简单动态图

python 复制代码
 die.py
==============================================
from random import randint

class Die:
    """表示一个骰子的类"""

    def __init__(self, num_sides=6):
        """骰子默认为 6 面的"""
        self.num_sides = num_sides

    def roll(self):
        """"返回一个介于 1 和骰子面数之间的随机值"""
        return randint(1, self.num_sides)
python 复制代码
die_roll.py
===============================================
import plotly.express as px
from die import Die

# 创建一个 D6
die = Die()

# 掷几次骰子并将结果存储在一个列表中
results = []
for roll_num in range(1000):
    result = die.roll()
    results.append(result)
print(f"打印结果 = {results}")

# 分析结果,统计结果频率。 frequencies=频率

frequencies = []
poss_results = range(1, die.num_sides + 1)  # 1-6
for num in poss_results:
    # 统计数字大小
    frequency = results.count(num)
    frequencies.append(frequency)
print(frequencies)

# 绘制直方图
# fig = px.bar(x=poss_results, y=frequencies)
# 绘制数据点
# fig = px.scatter(x=poss_results, y=frequencies)
# 绘制折线图
# fig = px.line(x=poss_results, y=frequencies)


# 设置图片属性
title = "总标题"
labels = {'x': 'x标题', 'y': 'y标题'}
fig = px.bar(x=poss_results, y=frequencies, title=title, labels=labels)
# X轴指定坐标间隔
fig.update_layout(xaxis_dtick=1)
# 保存图片
fig.write_html("../picture/1.html")

# 展示图片
fig.show()



5 csv文件处理

5.1 简单读取csv

python 复制代码
import csv
from pathlib import Path

path = Path('./datas/sitka_weather_07-2021_simple.csv')
# 获取文件所有行,return列表
lines = path.read_text().splitlines()
# 创建reader迭代器
reader = csv.reader(lines)
# 内容头
file_head = next(reader)
# 便利数据
for index, row in enumerate(reader):
    print(f"{index:>2}   {row}")

5.2 datetime 时间格式

5.2.1 相关参数 :---:

代码 含义 取值范围 示例
完整日期时间 %Y-%m-%d %H:%M:%S
年份
%Y 四位数的年份(带世纪) 0001 ~ 9999 2026
%y 两位数的年份(不带世纪) 00 ~ 99 26
月份
%m 两位数的月份(带前导零) 01 ~ 12 08
%b 缩写的月份名称(本地化) 英文:Jan~Dec Aug
%B 完整的月份名称(本地化) 英文:January~December August
日期
%d 两位数的日(带前导零) 01 ~ 31 01
%j 年中的第几天(带前导零) 001 ~ 366 213(8月1日)
%w 星期几的数字(0=周日,6=周六) 0 ~ 6 6(周六)
%A 完整的星期名称(本地化) Monday~Sunday Saturday
%a 缩写的星期名称(本地化) Mon~Sun Sat
%U 年中的第几周(周日作为一周第一天) 00 ~ 53 30
%W 年中的第几周(周一作为一周第一天) 00 ~ 53 30
小时
%H 24小时制的小时(带前导零) 00 ~ 23 14
%I 12小时制的小时(带前导零) 01 ~ 12 02(下午2点)
%p 上午/下午标识(本地化) AM, PM PM
分钟
%M 分钟(带前导零) 00 ~ 59 05
%S (带前导零) 00 ~ 59 09
%f 微秒(6位,带前导零) 000000 ~ 999999 123456
秒钟
%z UTC偏移量 (格式 ±HHMM±HH:MM +0000 ~ +2359, -0000 ~ -2359 +0800(北京时间)
%Z 时区名称(若无可为空) UTC, CST CST
特殊
%c 本地日期和时间表示 Sat Aug 1 14:05:09 2026
%x 本地日期表示 08/01/26(美国格式)
%X 本地时间表示 14:05:09
%% 转义字符,表示一个 % %

5.2.2 datetime.strftime(格式str)

python 复制代码
from datetime import datetime

# 创建一个 datetime 对象(2026年8月1日 14:05:09,微秒123456)
dt = datetime(2026, 8, 1, 14, 5, 9, 123456)

# 各种格式化组合
print(dt.strftime("%Y-%m-%d"))                 # 2026-08-01
print(dt.strftime("%y/%m/%d"))                 # 26/08/01
print(dt.strftime("%B %d, %Y"))                # August 01, 2026
print(dt.strftime("%b %d, %Y"))                # Aug 01, 2026
print(dt.strftime("%A, %B %d"))                # Saturday, August 01
print(dt.strftime("%I:%M %p"))                 # 02:05 PM
print(dt.strftime("%H:%M:%S.%f"))              # 14:05:09.123456
print(dt.strftime("%Y-%m-%d %H:%M:%S %Z"))     # 2026-08-01 14:05:09 (时区可能为空)
print(dt.strftime("%j"))                       # 213 (当年第213天)
print(dt.strftime("%w"))                       # 6 (周六)
print(dt.strftime("Today is %A, week %W"))     # Today is Saturday, week 30
print(dt.strftime("本地日期时间: %c"))          # 本地日期时间: Sat Aug  1 14:05:09 2026

5.2.3 datetimes.strptime(时间str,格式str)

python 复制代码
from datetime import datetime

# p1=日期字符串,p2=时间格式
first_date = datetime.strptime('2021-07-01', '%Y-%m-%d')
print(first_date)	# 2021-07-01 00:00:00

date_str = "2026-08-01 14:05:09"
dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(dt)  # 2026-08-01 14:05:09

# 解析不同格式
date_str2 = "Aug 01, 2026"
dt2 = datetime.strptime(date_str2, "%b %d, %Y")
print(dt2)  # 2026-08-01 00:00:00

# 解析包含AM/PM的时间
time_str = "02:05 PM"
dt3 = datetime.strptime(time_str, "%I:%M %p")
print(dt3)  # 1900-01-01 14:05:00 (年份会被补为1900,因为我们没提供年份)



6 ##########API




########## TimeStamp 时间格式




########## pandas 数据分析库

  • Pandas 是 Python 里最强大的数据分析和处理库,它提供了两个核心数据结构:

    • Series:一维带标签数组,类似 Excel 的一列。DataFrame 可视为由多个 Series 组成
    • DataFrame:二维表格,类似 Excel 的一张表。
  • 安装:pip install pandas

## 简单使用

python 复制代码
import pandas as pd

# 创建简单的DataFrame对象
data = {'Name': ['Google', 'Runoob', 'Taobao'], 'Age': [25, 30, 35]}
df = pd.DataFrame(data)
print(df)


# 创建两个Series对象
series_apples = pd.Series([1, 3, 7, 4])
series_bananas = pd.Series([2, 6, 3, 5])
# 将两个Series对象相加,得到DataFrame,并指定列名
df = pd.DataFrame({ 'Apples': series_apples, 'Bananas': series_bananas })
# 显示DataFrame
print(df)

# Series 列/一维数组

  • 每个元素都有索引,并且可以通过索引访问元素
  • 使用NaN表示空数据
python 复制代码
import pandas as pd

# 创建一个Series对象,指定名称为'A',值分别为1, 2, 3, 4
# 默认索引为0, 1, 2, 3
series = pd.Series([1, 2, 3, 4], name='A')

# 打印Series对象
print(series)

# 如果你想要显式地设置索引,可以这样做:
custom_index = [1, 2, 3, 4]  # 自定义索引
series_with_index = pd.Series([1, 2, 3, 4], index=custom_index, name='A')

# 显示带有自定义索引的Series对象
print(series_with_index)

# ds版本pandas

🐼 Pandas 完全学习指南

你好!非常高兴陪你一起学习 Pandas。我会按照从零开始、由浅入深的方式,把 Pandas 的核心知识拆解成几个模块,每个模块都会配合示例代码和解释。学完之后你就能独立用 Pandas 做数据处理了。


1. 认识 Pandas 和安装

Pandas 是 Python 里最强大的数据分析和处理库,它提供了两个核心数据结构:

  • Series:一维带标签数组,类似 Excel 的一列。
  • DataFrame:二维表格,类似 Excel 的一张表。

安装

bash 复制代码
pip install pandas

如果你用的是 Anaconda,Pandas 已经预装了。

导入约定:

python 复制代码
import pandas as pd
import numpy as np   # 通常会一起用

2. 核心数据结构

2.1 Series 系列

你可以把 Series 想象成"带索引的列表"。

python 复制代码
# 从列表创建
s = pd.Series([10, 20, 30, 40])
print(s)
# 0    10
# 1    20
# 2    30
# 3    40
# dtype: int64

你可以看到左边 0,1,2,3 是索引 ,右边是值。

给索引取个名字,或者自定义索引:

python 复制代码
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
print(s['a'])   # 10

Series 也有很多属性和方法:

python 复制代码
s.values      # array([10, 20, 30])
s.index       # Index(['a', 'b', 'c'])
s.dtype       # 数据类型
s.describe()  # 描述统计

2.2 DataFrame 数据框

DataFrame 是 Pandas 的核心,可以认为是"多个 Series 组成的字典"。

python 复制代码
data = {
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, 30, 22],
    '城市': ['北京', '上海', '广州']
}
df = pd.DataFrame(data)
print(df)

输出:

复制代码
   姓名  年龄  城市
0  张三  25  北京
1  李四  30  上海
2  王五  22  广州

常用属性:

python 复制代码
df.shape        # (3, 3)  行数、列数
df.columns      # 列名
df.index        # 行索引
df.dtypes       # 每列数据类型
df.info()       # 整体信息
df.describe()   # 数值列的统计

3. 数据读取与写入

Pandas 支持多种格式,最常用的是 CSV 和 Excel。

3.1 读取 CSV

python 复制代码
df = pd.read_csv('file.csv')
# 常用参数
df = pd.read_csv('file.csv', sep=',', header=0, encoding='utf-8', index_col=0)

3.2 读取 Excel

python 复制代码
df = pd.read_excel('file.xlsx', sheet_name='Sheet1')

3.3 写入文件

python 复制代码
df.to_csv('output.csv', index=False)    # 不保存行索引
df.to_excel('output.xlsx', index=False)

注意 :读取中文文件时,如果报编码错误,试试 encoding='gbk''utf-8'


4. 数据查看与选择(极其重要)

假设我们有如下数据:

python 复制代码
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六'],
    '年龄': [25, 30, 22, 28],
    '城市': ['北京', '上海', '广州', '深圳'],
    '工资': [8000, 12000, 9000, 11000]
})

4.1 查看前几行/后几行

python 复制代码
df.head(2)   # 前2行
df.tail(2)   # 后2行
df.sample(3) # 随机抽样3行

4.2 选择列

python 复制代码
df['姓名']           # 返回 Series
df[['姓名', '工资']]  # 返回 DataFrame

4.3 选择行:lociloc

  • loc :基于标签(索引名)选择。
  • iloc :基于整数位置选择。
python 复制代码
# 设定姓名为索引方便演示
df2 = df.set_index('姓名')

# loc 用索引名
print(df2.loc['张三'])              # 张三那一行(Series)
print(df2.loc[['张三','王五']])      # 多行

# iloc 用位置
print(df.iloc[0])                  # 第一行
print(df.iloc[0:2])                # 第0到1行(左闭右开)

4.4 条件选择(布尔索引)

python 复制代码
# 年龄大于25的行
df[df['年龄'] > 25]

# 多条件:城市是北京 且 工资大于8000
df[(df['城市'] == '北京') & (df['工资'] > 8000)]

# query 方法(更易读)
df.query('年龄 > 25 and 工资 > 9000')

4.5 行列同时选择

使用 lociloc 可以同时选行和列:

python 复制代码
# loc[行标签, 列标签]
df2.loc['张三', '工资']               # 单个值
df2.loc[['张三','李四'], ['年龄','工资']] # 子DataFrame

# iloc[行位置, 列位置]
df.iloc[0:2, [0,3]]   # 前两行,第0列和第3列

5. 数据清洗

真实数据往往是脏的:有缺失值、重复值、异常格式。

5.1 处理缺失值

python 复制代码
# 检测缺失
df.isnull()       # 整个表布尔
df['年龄'].isna() # 某列的缺失

# 删除有缺失的行
df.dropna()
# 删除某列缺失的行
df.dropna(subset=['年龄'])

# 填充缺失
df.fillna(0)                     # 填0
df['年龄'].fillna(df['年龄'].mean()) # 填平均值
df.fillna(method='ffill')        # 向前填充(用上一个有效值)

5.2 处理重复值

python 复制代码
df.duplicated()           # 返回布尔Series,标记是否重复
df.drop_duplicates()      # 删除重复行
df.drop_duplicates(subset=['姓名'])  # 基于某列去重

5.3 数据类型转换

python 复制代码
df.dtypes
df['工资'] = df['工资'].astype(float)
# 或者用 pd.to_numeric
df['工资'] = pd.to_numeric(df['工资'], errors='coerce')

errors='coerce' 会把无法转换的变成 NaN。

5.4 列名与索引处理

python 复制代码
df.columns = ['name', 'age', 'city', 'salary']  # 重设列名
df.rename(columns={'姓名': 'Name'}, inplace=True) # 部分重命名
df.index = range(len(df))   # 重置为0,1,2...
df.reset_index(drop=True)   # 重置索引且不保留旧索引

5.5 异常值处理

通常用统计方法或业务逻辑处理,例如保留工资在 5000~20000 之间的:

python 复制代码
df = df[(df['工资'] >= 5000) & (df['工资'] <= 20000)]

或者用分位数方法:

python 复制代码
q1 = df['工资'].quantile(0.25)
q3 = df['工资'].quantile(0.75)
iqr = q3 - q1
df = df[(df['工资'] >= q1 - 1.5*iqr) & (df['工资'] <= q3 + 1.5*iqr)]

6. 数据变换与操作

6.1 排序

python 复制代码
df.sort_values('工资')               # 升序
df.sort_values('工资', ascending=False) # 降序
df.sort_values(['城市', '工资'], ascending=[True, False])  # 多列排序

6.2 增加列/修改列

python 复制代码
df['税后工资'] = df['工资'] * 0.8
# 或者用 assign 返回新 DataFrame(不修改原数据)
df_new = df.assign(税后工资 = df['工资'] * 0.8)

6.3 删除列

python 复制代码
del df['城市']             # 直接删除原列
df.drop('城市', axis=1)    # 返回新DataFrame
df.drop(['城市', '年龄'], axis=1, inplace=True)

6.4 应用函数

  • 对一列df['工资'].apply(lambda x: x + 500)
  • 对一行df.apply(lambda row: row['工资'] * 1.1, axis=1)
  • 对整个表逐元素df.applymap(lambda x: str(x).upper())
python 复制代码
# 将城市列变成大写
df['城市'] = df['城市'].apply(str.upper)

6.5 映射与替换

python 复制代码
# map: 用字典映射
city_map = {'北京': 'BJ', '上海': 'SH', '广州': 'GZ', '深圳': 'SZ'}
df['城市代码'] = df['城市'].map(city_map)

# replace 替换值
df['城市'].replace('北京', 'BeiJing', inplace=True)

7. 分组与聚合

分组操作类似 SQL 中的 GROUP BY,极其常用。

python 复制代码
# 按城市分组,计算平均工资
df.groupby('城市')['工资'].mean()

# 多列聚合
df.groupby('城市').agg({
    '工资': 'mean',
    '年龄': ['min', 'max']
})

# 使用 agg 自定义
df.groupby('城市').agg(
    平均工资=('工资', 'mean'),
    人数=('姓名', 'count')
)

分组遍历

python 复制代码
for name, group in df.groupby('城市'):
    print(name, group.shape)

transform(不减少行数)

python 复制代码
# 给每个人加上"所在城市的平均工资"这一列
df['城市平均工资'] = df.groupby('城市')['工资'].transform('mean')

8. 合并与连接

数据通常来自多个表,需要拼接。

8.1 纵向拼接 concat

python 复制代码
df1 = pd.DataFrame({'A': [1,2]})
df2 = pd.DataFrame({'A': [3,4]})
pd.concat([df1, df2], ignore_index=True)   # 索引重新编号

8.2 横向拼接 merge(类似 SQL JOIN)

python 复制代码
left = pd.DataFrame({'key': ['A','B','C'], '值': [1,2,3]})
right = pd.DataFrame({'key': ['A','B','D'], '值2': [4,5,6]})

# 内连接
pd.merge(left, right, on='key', how='inner')
# 左连接
pd.merge(left, right, on='key', how='left')
# 全连接
pd.merge(left, right, on='key', how='outer')

8.3 join 按索引连接

python 复制代码
left.set_index('key').join(right.set_index('key'))

9. 时间序列处理

Pandas 对时间数据有强大支持。

9.1 生成时间序列

python 复制代码
dates = pd.date_range('2023-01-01', periods=6, freq='ME')  # 月底
ts = pd.Series(np.random.randn(6), index=dates)

9.2 转换时间格式

python 复制代码
df['日期'] = pd.to_datetime(df['日期字符串'])

9.3 时间索引的方便功能

python 复制代码
ts['2023-01']   # 切片某月
ts.resample('YE').mean()  # 按年重采样求均值

9.4 提取时间特征

python 复制代码
df['年'] = df['日期'].dt.year
df['月'] = df['日期'].dt.month
df['星期'] = df['日期'].dt.dayofweek

10. 常用数据分析与统计

python 复制代码
df['工资'].mean()   # 平均值
df['工资'].median() # 中位数
df['工资'].std()    # 标准差
df['工资'].value_counts()   # 频数统计
df['城市'].unique()         # 唯一值
df.corr()                   # 相关性矩阵(数值列)
df.cov()                    # 协方差矩阵

11. 可视化(简易)

Pandas 封装了 Matplotlib,可以直接画图。

python 复制代码
import matplotlib.pyplot as plt

df['工资'].plot(kind='hist')
plt.show()

df.plot(x='姓名', y='工资', kind='bar')
plt.show()

df.plot(kind='scatter', x='年龄', y='工资')
plt.show()

当然,深入可视化建议学习 Matplotlib 或 Seaborn。


12. 性能优化小技巧

  • 尽量使用向量化操作 :避免对每一行循环 for,多用 apply 和 Pandas 内置函数。
  • 使用合适的数据类型int8int64 省内存,可以用 df.memory_usage(deep=True) 检查。
  • 分批读取大文件pd.read_csv('large.csv', chunksize=100000)
  • 使用 queryeval 加速表达式计算。

13. 实战小案例

把上面的知识串起来,完成一个简单数据分析任务:

python 复制代码
# 模拟数据
data = {
    '姓名': ['A','B','C','D','E'],
    '年龄': [23,35,28,40,29],
    '城市': ['北京','上海','广州','深圳','上海'],
    '工资': [7000,12000,9000,15000,11000],
    '入职日期': ['2021-06-01','2019-03-15','2022-07-20','2018-01-10','2020-11-11']
}
df = pd.DataFrame(data)

# 1. 转换日期
df['入职日期'] = pd.to_datetime(df['入职日期'])

# 2. 新增一列:工龄(年)
df['工龄'] = (pd.Timestamp('today') - df['入职日期']).dt.days / 365

# 3. 筛选:工资大于 10000 且 城市为上海的员工
high_pay_sh = df[(df['工资'] > 10000) & (df['城市'] == '上海')]

# 4. 按城市分组,计算平均工资、最大年龄
summary = df.groupby('城市').agg(
    平均工资=('工资','mean'),
    最大年龄=('年龄','max')
)
print(summary)

14. 学习建议与资源

  1. 官方文档https://pandas.pydata.org/docs/ (最权威)
  2. 《利用Python进行数据分析》:Pandas 作者 Wes McKinney 写的书。
  3. 动手练习:去 Kaggle 找一些数据集(如泰坦尼克号),用 Pandas 探索数据。
  4. 记住常用模式:读取→查看→清洗→变换→分析→可视化。
  5. 善用 df.info()df.head():每拿到一个新数据,先用它们了解结构。

15. 总结

Pandas 的核心能力可以概括为:

操作 核心方法
数据读取 read_csv, read_excel
数据查看 head, info, describe, shape
行列选择 loc, iloc, 布尔条件
数据清洗 dropna, fillna, drop_duplicates
变换 apply, map, replace, sort_values
分组聚合 groupby, agg, transform
合并 concat, merge, join
时间序列 to_datetime, date_range, dt 访问器

掌握这些,你就已经具备处理 80% 以上结构化数据任务的能力了。接下来最重要的是动手写代码,遇到问题再去查文档。祝你学习顺利,有任何细节想深入了解,随时再问我!

相关推荐
海上小飞龙1 小时前
Redis 分布式锁原理:从 SET NX EX 到 Redisson 看门狗
数据库·redis·分布式
码事漫谈1 小时前
把 AI 拆掉,你的系统还能跑吗?
前端·后端
沫璃染墨1 小时前
《从零入门Linux系统篇(十五):系统工具篇·六——GDB调试器详解:从程序执行控制到高级调试技巧》
linux·运维·服务器·c语言·c++
Hammer_Hans2 小时前
DFT笔记98
java·开发语言·数据库
AIgorithmGEEK2 小时前
Linux 环境变量完全指南:从入门到理解底层原理
linux·运维·服务器
默_笙2 小时前
⛵ 我用 React + TS 做了个"调色盘",顺便学会了企业级项目的目录架构
前端·javascript
预知同行2 小时前
从 MCP 到 CLI:AI Agent 工具链的架构演进与实战抉择
前端·面试
何智超2 小时前
AI 微前端性能优化之旅(中):重启
前端·vibecoding
渣波2 小时前
拒绝 Redux 样板代码:Zustand 核心原理与实战进阶指南(基础、异步与切片模式)
前端·javascript
迪康Defender3 小时前
从静态存储到动态流转:终端透明加密两种模式实战解析
运维·服务器·网络·数据库·其他