【Python】模块与包

目录

      • 模块基本概念
        • 命名空间
        • 模块
      • 标准库 - sys
        • 命令行
        • 模块搜索路径
        • 操作系统平台标识符
        • Python 解释器版本
        • 流与缓冲区
        • 退出状态码
        • 递归深度限制
        • 对象内存占用
        • 引用计数
        • sys 是干什么的
      • 标准库 - os
        • os 是干什么的
        • 目录管理
        • 文件操作
      • 标准库 - os.path
        • os.path 是干什么的
        • 路径拼接
        • 路径拆分
        • 路径检查
      • 标准库 - shutil
        • shutil 是干什么的
        • 复制
        • 删除
        • 移动
      • 标准库 - csv
        • 存储表格数据
        • 内置函数 - 序列化和反序列化
        • csv 是干什么的
        • 写入和读取列表
        • 写入和读取字典
        • 关于恶意代码注入的风险
      • 标准库 - datetime
        • datetime 是干什么的
        • 日期
        • 时间
        • 时间段
        • 运算
        • 常用指示符
      • 标准库 - time
        • time 是干什么的
      • 标准库 - math
        • 数学常数
        • 排列组合
        • 数论
        • 取整
        • 极限
        • 对数
        • 向量
        • 角度制与弧度制
        • 三角函数
        • 双曲函数
        • 特殊函数
      • 标准库 - random
        • 随机数生成
        • 随机抽样
        • 洗牌/打乱
        • 正态分布
      • 标准库 - collections
        • collections 是干什么的
        • 频次统计
        • 双端队列
        • 双端队列 - 滑动窗口
      • 标准库 - itertools
        • itertools 是干什么的
        • 无限迭代器
        • 迭代器处理 - 预处理
        • 迭代器处理 - 重复
        • 迭代器处理 - 拼接
        • 迭代器处理 - 切片
        • 迭代器处理 - 复制
        • 迭代器处理 - 分组

模块基本概念

命名空间

存放变量的场所被称为 命名空间命名空间 有局部、全局和内置的,还有对象中的嵌套命名空间(在方法之内)。

模块

此前你一直在 your_script.py 中编辑代码,这种 脚本文件 被称为 模块模块 是 Python 代码的一种 组织单位

你写了 那么多次,应该清楚,不同模块之间的代码 都是互不影响的,以至于你要复用代码经常需要 在多个模块之间 复制再粘贴。

听到这里,你一定猜到,接下来有什么可能会帮到你

一个 模块 内的 Python 代码通过 导入操作 就能够访问 另一个模块 内的代码。import 是Python的 关键字,它被用来构造 import 语句,可以在模块中进行 导入操作

导入操作 分为两个步骤,它先 搜索 指定名称的模块,然后将搜索结果 绑定 到当前作用域中的名称。

假设你有一个 your_module.py 模块:

def add(x, y): return x + y

导入模块的语法为:

import your_module

print(your_module.add(1, 1))

运行程序:

python your_script.py
### 控制台将打印
# 2

当语句包含 多个子句(由逗号分隔)时 导入将对每个子句 分别执行,如同这些子句被分成 独立的 import 语句 一样。

如果指定名称的模块未找到,则会引发 ModuleNotFoundError 异常:

import your_module

pass

运行程序:

python your_script.py
### 控制台将打印
# 
# Traceback (most recent call last):
#   File "your_script.py", line 3, in <module>
#     import your_module
# ModuleNotFoundError: No module named 'your_module'
# 
# Exited with error status 1

常用的功能 封装到模块中,可以在多个程序中 重复使用

一个模块 只会被导入一次,不管你执行了多少次 import 语句。这样可以 防止导入模块被一遍又一遍地执行

标准库 - sys

命令行

你经常在 控制台 输入:

python your_script.py

有没有想过为什么要这样写?

类似 python your_script.py 的字符序列 被称为 命令行控制台执行命令行的窗口

命令行第一个单词,即诸如pythonpipnpm等字符,被称为 命令,表示要调用的 可执行程序

python # 一堆乱七八糟的东西
# 启动 Python 解释器
pip # 一堆乱七八糟的东西
# 调用 Python 包管理工具
npm # 一堆乱七八糟的东西
# 调用 Node.js 包管理工具

本质上 命令 对应操作系统中的 一个可执行文件(如 /usr/bin/python)。

命令行 命令 之后的 所有内容,被称为 命令行参数,用于向 程序 传递信息,可分为:

  • 脚本或目标文件:如 your_script.py,Python 解释器的参数;
  • 脚本参数:如你可能会输入的 arg1–option=value字符序列

命令行参数 其作用为控制 程序 的行为或为其提供数据。

注意:

你的 your_script.py,它 不是程序不是程序不是程序

python 才是 程序your_script.py程序的参数,告诉 程序,它要处理什么。

模块搜索路径

当你 import module 时,Python 会按照 一个预定顺序 搜索模块,因为 它并不能事先知道你有什么模块

  • 通常Python会从 当前脚本所在的目录 开始搜索,注意:不是当前工作目录
  • 若搜索不到,则按照你 当初安装Python时设置的环境变量 那寻找。
  • 再找不到,就搜索Python安装的标准库 目录。
  • 最后搜索通过 pip 安装的第三方模块目录。
操作系统平台标识符

你知道你所用的操作系统 是什么类型,但是 你的脚本并不知道,同样的,你不知道你的脚本 未来在被其他开发者所用时,他们用的 操作系统类型

要清楚,程序一旦跨平台,当前的语法 很可能不适用,比如Python就有不少标准库需要区分Windows操作系统和Linux操作系统,否则会出现严重的 兼容问题,你的脚本倘若需要被其他人使用处理不同操作系统是很有必要的

每一种操作系统都自带 标识符

常见标识符操作系统
win32Windows
linuxLinux
darwinmacOS
cygwinCygwin 环境下的 Winows
aixIBM AIX
freebsd/openbsdFreeBSD/OpenBSD
Python 解释器版本

Python 可以说是一门 十分大胆的编程语言,它与C++ 并不相同,它并不像C++那样每个版本都要向下做大量兼容

因此Python不同版本之间,特别是 Python 2.x 与 Python 3.x 之间,被戏称成两门语言语法基本不兼容

同样的理由,如果脚本要写给其他人使用,判断代码 是否支持当前 Python 环境,避免程序在不同版本中 因语法、模块、第三方库不兼容导致崩溃,是必须的。

流与缓冲区

数据流,简称 ,是计算机领域中的抽象概念,数据的 连续输入 或者 连续输出 就是 ,通常分为几种类型:

  • 输入流:数据 从外部(如文件、键盘、网络)流向 程序;
  • 输出流:数据从程序 流向外部(如屏幕、文件、网络)
  • 标准输入流:数据 从键盘流向 程序;
  • 标准输出流:数据从程序 流向屏幕
  • 标准错误流错误信息 从程序 流向屏幕
  • 文件流:数据 从文件流向 程序,或者从程序 流向文件
  • 网络流:数据 从网络流向 程序,或者从程序 流向网络
  • 内存流:数据 在内存中流动

有一块 临时存储数据的内存区域,称为 缓冲区,用来减少 频繁的输入输出操作。数据一般通过 进入 缓冲区,然后才执行操作。

缓冲区 分为 全缓冲行缓冲无缓冲(没有缓冲区也是一种缓冲区)

  • 全缓冲缓冲区 满时才写入目标;
  • 行缓冲:遇到 换行符 就可以写入目标;
  • 无缓冲直接写入 目标。

读取写入刷新关闭等关键操作:

  • 读取:从输入流中 获取数据
  • 写入:向输出流中 发送数据
  • 刷新:强制 将缓冲区数据写入目标
  • 关闭:释放流 所占用的资源

如果更改 标准流的默认流向,使得程序或系统从 非默认输入流 获取数据,或者从 非默认输出流 发送数据,称为 重定向,这通常为了 持久化存储数据 或者单纯是为了 **调试或隔离错误 ** 才有的操作。

退出状态码

程序结束运行时 会给操作系统返回一个整数值 ,在 0-255 之间,用于表示程序的 执行结果错误类型,被称为 退出状态码

常见 退出状态码 表示的含义:

退出状态码含义使用场景
0成功程序正常完成预期任务
1通用错误未知错误或未分类错误
2命令行参数或语法错误用户输入参数不符合要求
126权限不足(UNIX)尝试执行无权限的文件
127文件未找到(UNIX)调用了不存在的文件货脚步
130进程被终止(如Ctrl + C)用户强制中断程序
递归深度限制

Python 解释器为了防止 无限递归 导致的 栈溢出,会给 递归深度 设置限制,默认值是 1000 层,这是平衡 安全性能 的折中选择,超过此限制会触发 RecursionError 异常。

某些算法可能需要 超过默认递归深度 ,因此必要时可能需要 更改递归深度限制

然而,最好的办法还是使用 迭代循环 ,而非 递归函数调用

对象内存占用

Python 中每个对象都包含 元数据,这些信息由解释器内部管理,但 对用户透明。用户只能看到对象的 内存开销,看不到其中的元数据。

这是为 性能安全 考量,因为需要 递归遍历每一个元素,而且某些对象的内部结构是 动态的,代价高昂且 会暴露解释器内部的细节,仅知道大概的内存占用已经足够用于调试和优化了。

引用计数

每个 Python 对象内部都维护着一个 计数器,记录当前有多少个 变量数据结构其他对象 指着它,数值称为 引用计数

每个对象 刚创建时引用计数1,此后每个新增引用,计数器都会 +1,变量被重新赋值删除离开作用域 时,计数器 -1

当引用计数降为 0,对象占用的内存会被解释器 立即回收

然而,如果存在两个对象 互相应用,引用计数会 永远无法归零,造成 内存泄露

sys 是干什么的

sys 是处理和 系统相关标准库,如 与解释器交互 或对 系统功能的访问。因此,使用 sys 稍不注意会引发风险。

sys.argv 存储 命令行参数 列表

import sys

print(sys.argv)

运行程序:

python your_script.py
### 控制台将打印
# ['your_script.py']

sys.path模块搜索路径 列表,可动态修改:

import sys

print(sys.path)

print('|------------')
sys.path.append('/your/module/path')
print(sys.path)

运行程序:

python your_script.py
# ['/box', '/usr/local/python-3.8.1/lib/python38.zip', '/usr/local/python-3.8.1/lib/python3.8', '/usr/local/python-3.8.1/lib/python3.8/lib-dynload', '/usr/local/python-3.8.1/lib/python3.8/site-packages']
# |------------
# ['/box', '/usr/local/python-3.8.1/lib/python38.zip', '/usr/local/python-3.8.1/lib/python3.8', '/usr/local/python-3.8.1/lib/python3.8/lib-dynload', '/usr/local/python-3.8.1/lib/python3.8/site-packages', '/your/module/path']

sys.platform当前操作系统的平台标识符

import sys

print(sys.platform)

运行程序:

python your_script.py
### 控制台将打印
# linux

sys.version 是 **当前 Python 解释器的版本信息字符串,sys.version_info 则是元组:

import sys

print(sys.version)
print(sys.version_info)

运行程序:

python your_scripy.py
### 控制台将打印
# 3.8.1 (default, Dec 29 2019, 00:47:39) 
# [GCC 8.3.0]
# sys.version_info(major=3, minor=8, micro=1, releaselevel='final', serial=0)

sys.stdinsys.stdoutsys.stderr标准输入流标准输出流标准错误流

重定向标准输出流

import sys

original_stdout = sys.stdout

with open('your_stdout.txt', 'w', encoding = 'utf-8') as f:
    sys.stdout = f
    print('这句话会被写入 your_stdout.txt 文件。')

运行程序:

python your_script.py

现在工作目录因此创建了 your_stdout.txt 文件,打开可以看到:

这句话会被写入 your_stdout.txt 文件。

重定向标准错误流

import sys

with open('your_errors.log', 'w') as f:
    print(1/0)

运行程序:

python your_script.py

现在工作目录因此创建了 your_errors.txt 文件。

sys.exit() 退出程序,并返回退出状态码:

import sys

print('你能看到这行字')
sys.exit(130)
print('你不能看到这行字')

运行程序:

python your_script.py
### 控制台将打印
# 你能看到这行字
# 
# 
# Exited with error status 130

sys.getrecursionlimit() 获取 递归深度限制sys.setrecursionlimit() 修改 递归深度限制

import sys

print(sys.getrecursionlimit())

sys.setrecursionlimit(10)

def test(n = 7):
    print(n)
    test(n - 1)
    if n == 0:
        return

test()

运行程序:

python your_script.py
### 控制台将打印
# 1000
# 7
# 6
# 5
# 4
# 3
# 2
# 1
# 
# Traceback (most recent call last):
#   File "your_script.py", line 15, in <module>
#     test()
#   File "your_script.py", line 11, in test
#     test(n - 1)
#   File "your_script.py", line 11, in test
#     test(n - 1)
#   File "your_script.py", line 11, in test
#     test(n - 1)
#   [Previous line repeated 4 more times]
#   File "your_script.py", line 10, in test
#     print(n)
# RecursionError: maximum recursion depth exceeded while getting the str of an object
# 
# Exited with error status 1

sys.getsizeof() 返回对象占用的字节数:

import sys

print(sys.getsizeof([1, 1, 4, 5, 1, 4]))

运行程序:

python your_script.py
### 控制台将打印
# 104

sys.getrefcount() 返回对象的 引用计数

import sys

x = 8000
print(sys.getrefcount(x))

y = x
print(sys.getrefcount(x))

a = []
a.append(x)
print(sys.getrefcount(x))

运行程序:

python your_script.py
### 控制台将打印
# 4
# 5
# 6

标准库 - os

os 是干什么的

os 是处理和 操作系统交互标准库,如 文件操作目录管理进程控制环境变量访问等。

目录管理

os.getcwd() 获取当前工作目录:

import os

print(os.getcwd())

运行程序:

python your_script.py
### 控制台将打印
# /box

os.listdir() 列出当前层级目录下所有文件和子目录:

import os

print(os.listdir())

运行程序:

python your_script.py
### 控制台将打印
# ['run', 'your_script.py']

os.mkdir() 创建目录:

import os

print(os.listdir())

os.mkdir('logs')
print(os.listdir())

运行程序:

python your_script.py
### 控制台将打印
# ['your_script.py', 'run']
# ['your_script.py', 'logs', 'run']

os.rename 重命名目录:

import os

print(os.listdir())

os.mkdir('logs')
print(os.listdir())

os.rename('logs', 'tmp')
print(os.listdir())

运行程序:

python your_script.py
### 控制台将打印
# ['your_script.py', 'run']
# ['your_script.py', 'logs', 'run']
# ['tmp', 'your_script.py', 'run']

os.chdir() 切换当前工作目录:

import os

print(os.getcwd())
print(os.listdir())

os.mkdir('logs')
print(os.listdir())

os.chdir('logs')
print(os.getcwd())

运行程序:

python your_script.py
### 控制台将打印
# /box
# ['your_script.py', 'run']
# ['your_script.py', 'logs', 'run']
# /box/logs

os.makedirs() 递归地创建多级目录,os.walk() 递归遍历目录树:

import os

print(os.getcwd())

os.makedirs('/box/hello/world/are_you/ok')

os.chdir('hello/world/are_you/ok')

print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
### 控制台将打印
# /box
# /box/hello/world/are_you/ok
# 目录:/box, 子目录:['hello'], 文件: ['your_script.py', 'run']
# 目录:/box/hello, 子目录:['world'], 文件: []
# 目录:/box/hello/world, 子目录:['are_you'], 文件: []
# 目录:/box/hello/world/are_you, 子目录:['ok'], 文件: []
# 目录:/box/hello/world/are_you/ok, 子目录:[], 文件: []
文件操作

由于大多数功能可以使用 内置函数 open() 替代,因此只讲部分:

os.remove() 删除文件:

import os

print(os.listdir())

with open('logs.txt','w'):
    pass
print(os.listdir())

os.remove('logs.txt')

print(os.listdir())

运行程序:

python your_script.py
### 控制台将打印
# ['your_script.py', 'run']
# ['logs.txt', 'your_script.py', 'run']
# ['your_script.py', 'run']

os.rename 重命名文件:

import os

print(os.listdir())

with open('tmp.txt', 'w'):
    pass
print(os.listdir())

os.rename('tmp.txt', 'logs.txt')
print(os.listdir())

运行程序:

python your_script.py
### 控制台将打印
# ['script.py', 'run']
# ['tmp.txt', 'your_script.py', 'run']
# ['logs.txt', 'your_script.py', 'run']

os.rename() 也可以用来移动文件:

import os

print(os.listdir())

os.mkdir('logs')
print(os.listdir())

with open('tmp.txt', 'w'):
    pass
print(os.listdir())

os.rename('tmp.txt', 'logs/tmp.txt')

for root, dirs, files in os.walk('logs'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
### 控制台将打印
# ['run', 'script.py']
# ['run', 'script.py', 'logs']
# ['run', 'tmp.txt', 'script.py', 'logs']
# 目录:logs, 子目录:[], 文件: ['tmp.txt']

标准库 - os.path

os.path 是干什么的

os.path处理文件路径 的核心工具,它提供了一系列函数来 解析拼接检查转换 文件路径。

路径拼接

在不同操作系统上,路径分隔符 不同(Windows 用 \,Linux/macOS 用 /)。

os.path.join() 将多个路径组件合并,自动添加合适的分隔符(跟随操作系统):

import os

path = os.path.join('aa','bb','cc.txt')
print(path)

运行程序:

python your_script.py
### 若操作系统是 Linux/macOS ,控制台将打印
# aa/bb/cc.txt
### 否则,将打印
# aa\bb\cc.txt
路径拆分

os.path.dirname() 提取路径的 目录名os.path.basename() 提取路径的 文件名

import os

path = os.path.join('aa','bb','cc.txt')

print(os.path.dirname(path))
print(os.path.basename(path))

运行程序:

python your_script.py
### 控制台将打印
# aa/bb
# cc.txt

os.path.splitext() 获取文件的 主文件名扩展名元组

import os

path = os.path.join('aa','bb','cc.txt')

file_name = os.path.basename()
print(os.path.splitext(file_name))

运行程序:

python your_script.py
### 控制台将打印
# ('cc', '.txt')
路径检查

os.path.exists() 检查路径是否存在:

import os
print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')
print('')

os.makedirs('logs/tmp/hello/world')
for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')
print('')

print(os.path.exists('logs/tmp/hello'))
print(os.path.exists('logs/abcd'))

运行程序:

python your_script.py
### 控制台将打印
# /box
# 目录:/box, 子目录:[], 文件: ['your_script.py', 'run']
# 
# 目录:/box, 子目录:['logs'], 文件: ['your_script.py', 'run']
# 目录:/box/logs, 子目录:['tmp'], 文件: []
# 目录:/box/logs/tmp, 子目录:['hello'], 文件: []
# 目录:/box/logs/tmp/hello, 子目录:['world'], 文件: []
# 目录:/box/logs/tmp/hello/world, 子目录:[], 文件: []
# 
# True
# False

os.path.isfile() 检查是否为文件,os.path.isdir(),检查是否为目录:

import os
print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')
print('')

os.makedirs('logs/tmp/hello/world')
with open('logs/tmp/oiiai.txt', 'w'):
    pass
for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')
print('')

print(os.path.isfile('logs/tmp/oiiai.txt'))
print(os.path.isfile('logs/tmp/hello'))
print(os.path.isdir('logs/tmp/oiiai.txt'))
print(os.path.isdir('logs/tmp/hello'))

运行程序:

python your_script.py
### 控制台将打印
# /box
# 目录:/box, 子目录:[], 文件: ['run', 'your_script.py']
# 
# 目录:/box, 子目录:['logs'], 文件: ['run', 'your_script.py']
# 目录:/box/logs, 子目录:['tmp'], 文件: []
#,目录:/box/logs/tmp, 子目录:['hello'], 文件: ['oiiai.txt']
# 目录:/box/logs/tmp/hello, 子目录:['world'], 文件: []
# 目录:/box/logs/tmp/hello/world, 子目录:[], 文件: []
# 
# True
# False
# False
# True

标准库 - shutil

shutil 是干什么的

shutil 是用于 递归地复制、删除 目录和文件的标准库。

复制

shutil.copy() 复制文件:

import os
import shutil

print(os.listdir())

os.mkdir('tmp')
print(os.listdir())

with open('logs.txt','w'):
    pass
print(os.listdir())

shutil.copy('logs.txt', 'tmp/logs.txt')

print(os.getcwd())
for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
### 控制台将打印
# ['run', 'script.py']
# ['run', 'tmp', 'script.py']
# ['run', 'logs.txt', 'tmp', 'script.py']
# /box
# 目录:/box, 子目录:['tmp'], 文件: ['run', 'logs.txt', 'script.py']
# 目录:/box/tmp, 子目录:[], 文件: ['logs.txt']

shutil.copytree() 递归地复制目录树:

import os
import shutil

os.makedirs('a/aa/aaa')
os.makedirs('a/aa/aab/aaba')
os.makedirs('a/aa/aab/aabb')
os.makedirs('a/ab/aba')
os.makedirs('a/ac')
os.makedirs('b')

print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

print('')
shutil.copytree('a', 'b/a')

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
# /box
# 目录:/box, 子目录:['b', 'a'], 文件: ['your_script.py', 'run']
# 目录:/box/b, 子目录:[], 文件: []
# 目录:/box/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/a/ac, 子目录:[], 文件: []
# 目录:/box/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/a/aa/aab/aabb, 子目录:[], 文件: []
# 
# 目录:/box, 子目录:['b', 'a'], 文件: ['your_script.py', 'run']
# 目录:/box/b, 子目录:['a'], 文件: []
# 目录:/box/b/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/b/a/ac, 子目录:[], 文件: []
# 目录:/box/b/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/b/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/b/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/b/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/b/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/b/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/b/a/aa/aab/aabb, 子目录:[], 文件: []
# 目录:/box/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/a/ac, 子目录:[], 文件: []
# 目录:/box/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/a/aa/aab/aabb, 子目录:[], 文件: []
删除

shutil.rmtree() 递归地删除目录树:

import os
import shutil

os.makedirs('a/aa/aaa')
os.makedirs('a/aa/aab/aaba')
os.makedirs('a/aa/aab/aabb')
os.makedirs('a/ab/aba')
os.makedirs('a/ac')

print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

print('')
shutil.rmtree('a')

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
### 控制台将打印
# /box
# 目录:/box, 子目录:['a'], 文件: ['your_script.py', 'run']
# 目录:/box/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/a/ac, 子目录:[], 文件: []
# 目录:/box/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/a/aa/aab/aabb, 子目录:[], 文件: []
# 
# 目录:/box, 子目录:[], 文件: ['your_script.py', 'run']
移动

shutil.move() 递归地移动目录:

import os
import shutil

os.makedirs('a/aa/aaa')
os.makedirs('a/aa/aab/aaba')
os.makedirs('a/aa/aab/aabb')
os.makedirs('a/ab/aba')
os.makedirs('a/ac')
os.makedirs('b')

print(os.getcwd())

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

print('')
shutil.move('a', 'b/a')

for root, dirs, files in os.walk('/box'):
    print(f'目录:{root}, 子目录:{dirs}, 文件: {files}')

运行程序:

python your_script.py
### 控制台将打印
# /box
# 目录:/box, 子目录:['b', 'a'], 文件: ['your_script.py', 'run']
# 目录:/box/b, 子目录:[], 文件: []
# 目录:/box/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/a/ac, 子目录:[], 文件: []
# 目录:/box/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/a/aa/aab/aabb, 子目录:[], 文件: []
# 
# 目录:/box, 子目录:['b'], 文件: ['your_script.py', 'run']
# 目录:/box/b, 子目录:['a'], 文件: []
# 目录:/box/b/a, 子目录:['ac', 'ab', 'aa'], 文件: []
# 目录:/box/b/a/ac, 子目录:[], 文件: []
# 目录:/box/b/a/ab, 子目录:['aba'], 文件: []
# 目录:/box/b/a/ab/aba, 子目录:[], 文件: []
# 目录:/box/b/a/aa, 子目录:['aaa', 'aab'], 文件: []
# 目录:/box/b/a/aa/aaa, 子目录:[], 文件: []
# 目录:/box/b/a/aa/aab, 子目录:['aaba', 'aabb'], 文件: []
# 目录:/box/b/a/aa/aab/aaba, 子目录:[], 文件: []
# 目录:/box/b/a/aa/aab/aabb, 子目录:[], 文件: []

标准库 - csv

存储表格数据

直接用 文件对象write() 对象方法 写入列表或字典,会遇到问题,因为 write() 方法需要的是字符串,而不是 列表字典

with open('write_list.txt', 'w', encoding = 'utf-8') as f:
    try:
        f.write([1, 2, 3])
    except TypeError:
        print('TypeError')
with open('write_dict.txt', 'w', encoding = 'utf-8') as f:
    try:
        f.write({'a':1, 'b':2, 'c':3})
    except TypeError:
        print('TypeError')

运行程序:

TypeError
TypeError
内置函数 - 序列化和反序列化

一种简单的方法就是每次都强制转化为字符串来存储,这被称为 序列化

with open('write_list.txt', 'w', encoding = 'utf-8') as f:
    f.write(str([1, 2, 3]))
    print('ok')
with open('write_dict.txt', 'w', encoding = 'utf-8') as f:
    f.write(str({'a':1, 'b':2, 'c':3}))
    print('ok')

运行程序:

python your_script.py
### 控制台将打印
# ok
# ok

eval() 是Python的 内置函数,可以将 字符串伪Python代码 转换为Python代码来 执行

def add(x, y):
    return x + y
eval("add(1, 2)")

运行程序:

python your_script.py
### 控制台将打印
# 3

因此可以用它把 数据还原

dict_to_str = str({'a':1, 'b':2, 'c':3})

print(isinstance(dict_to_str, dict))

str_to_dict = eval(dict_to_str)

print(isinstance(str_to_dict, dict))

运行程序:

python your_script.py
### 控制台将打印
# False
# True

这种把字符串还原为原数据的做法称为 反序列化

然而 eval()反序列化,会很 危险!,因为它的本质工作是 执行伪Python代码,如果有人写入了 恶意代码,你就会被 攻击

# 过于危险,不能演示!

def rm_all():
    import os
    import sys
    import shutil
    current_dir = os.getcwd()
    script_name = os.path.basename(sys.argv[0])
    
    for entry in os.listdir(current_dir):
        if entry == script_name:
            continue
        
        full_path = os.path.join(current_dir, entry)
        
        try:
            if os.path.isfile(full_path):
                os.remove(full_path)
            elif os.path.isdir(full_path):
                shutil.rmtree(full_path)
        except BaseException:
            continue
    
    return 0
with open('test.txt', 'w', encoding='utf-8') as f:
    f.write("rm_all()")

这被称为 恶意代码注入,除非你确信你的文件 没有被篡改,否则绝对不可使用 eval(),即使它能还原 所有类型的Python对象

csv 是干什么的

标准库 csv 用来 安全地 处理 表格数据序列化 写入文件或从文件 反序列化 读取 表格数据

csv文件,又叫 逗号分隔值文件,是一种纯文本格式,每行表示一条 记录字段 间用 逗号 分隔12

姓名,年龄,邮箱,地址
张三,25,zhangsan@example.com,北京市
李四,30,lisi@example.com,上海市
王五,28,wangwu@example.com,南京市

第一行通常为 标题 ,描述各列 含义,可以没有,只要你 记得住就行3

张三,25,zhangsan@example.com,北京市
李四,30,lisi@example.com,上海市
王五,28,wangwu@example.com,南京市

包含逗号的字段,需用 双引号 包裹整个字段:

"李四, 博士",30,lisi@example.com,"上海市, 浦东新区"

字段内的 双引号 需用 两个双引号表示:

"王五",28,"他说:""你好!""",南京市

连续逗号 表示 空值

张三,,zhangsan@example.com

注意,csv文件 不支持注释

写入和读取列表

csv.writer 是一个类,传入csv文件对象 可以实例化一个writer 对象,writer 对象负责将数据转换为 带分隔符的字符串

writer.writerow() 写入 单行表格数据writer.writerows() 写入 多行表格数据

import csv

data = [['Alice', 30], ['Bob', 25]]
with open('your_csv.csv', 'w', newline = '', encoding = 'utf-8') as f:
    writer = csv.writer(f)
    writer.writerow(['Name', 'Age'])
    writer.writerows(data)

运行程序:

python your_script.py

目录中出现了一个csv文件:

Name,Age
Alice,30
Bob,25

csv.reader 是一个类,传入csv文件对象 可以实例化一个reader 对象,它是一个迭代器

import csv

with open('your_csv.csv', newline = '', encoding = 'utf-8') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

运行程序:

python your_script.py
### 控制台将打印
# ['Name', 'Age']
# ['Alice', '30']
# ['Bob', '25']
写入和读取字典

csv.DictWriter 是一个类,传入csv文件对象标题,可以实例化一个 dict_writer 对象,操作类似 writer,但是会将 字典 映射到输出行。

dict_writer.writeheader() 写入标题,dict_writer.writerows() 写入字典:

import csv

header = ['Name', 'Age']
data = [
    {
        'Name': 'Alice',
        'Age': 30,
    },
    {
        'Name': 'Bob',
        'Age': 25,
    },
]

with open('your_csv.csv', 'w', encoding = 'utf-8') as f:
    dict_writer = csv.DictWriter(f, header)
    dict_writer.writeheader()
    dict_writer.writerows(data)

运行程序:

python your_script.py

目录中出现了一个csv文件:

Name,Age
Alice,30
Bob,25

csv.DictReader 是一个类,传入csv文件对象 可以实例化一个 dict_reader 对象,操作类似 reader,但是会将输入行 映射到 字典,它是一个迭代器

import csv

with open('your_csv.csv', encoding = 'utf-8') as f:
    dict_reader = csv.DictReader(f)
    for row in dict_reader:
        print(row)

运行程序:

python your_script.py
### 控制台将打印
# {'Name': 'Alice', 'Age': '30'}
# {'Name': 'Bob', 'Age': '25'}
关于恶意代码注入的风险

你可以放心的是,csv 不会直接执行或注入恶意代码,因为它仅用于解析和生成文本数据

标准库 - datetime

datetime 是干什么的

datetime 引入了新的数据类型,你学到现在,都不知道如何处理 日期和时间你应该没有意识到 你现在都没遇到处理 日期与时间 的问题。

数据类型获取方式含义
datetime.data由 datetime.data 类实例化一个理想化的简单型日期,它假设当今的公历在过去和未来永远有效。
datetime.time由 datetime.time 类实例化一个独立于任何特定日期的理想化时间,它假设每一天都恰好等于 246060 秒
datetime.datetime由 datetime.datetime 类实例化日期和时间的结合
datetime.timedelta由 datetime.timedelta 类实例化将两个 datetime 或 date 实例之间的差值表示为微秒级精度的持续时间
Object
date
year
month
day
replace()
weekday()
isoweekday()
strftime()
time
hour
minute
second
microsecond
replace()
strftime()
datetime
hour
minute
second
microsecond
date()
time()
timestamp()
fromtimestamp()
timedelta
days
seconds
minutes
hours
weeks
日期

datetime.data 数据类型的数据被称为 日期

import datetime

date = datetime.date(2023, 10, 25)
### 年,月,日
# 所有参数都是必要的
print(date)

运行程序:

python your_script.py
### 控制台将打印
# 2023-10-25

类方法 datetime.data.today() 返回 今天 的日期:

import datetime

print(datetime.date.today())

运行程序:

python your_script.py
### 控制台将打印
# 2025-03-26

对象方法 date.replace() 替换部分日期值:

import datetime

date = datetime.date(2023, 10, 25)
print(date)

new_date = date.replace(year = 2005)
print(new_date)

运行程序:

python your_script.py
### 控制台将打印
# 2023-10-25
# 2005-10-25

对象方法 date.weekday()date.isoweekday() 获取星期几:

import datetime

date = datetime.date.today()
print(date.weekday())
# 星期一: 0
# 星期二: 1
# 星期三: 2
# 星期四: 3
# 星期五: 4
# 星期六: 5
# 星期日: 6
print(date.isoweekday())
# 星期一: 1
# 星期二: 2
# 星期三: 3
# 星期四: 4
# 星期五: 5
# 星期六: 6
# 星期日: 7

运行程序:

python your_script.py
# 2
# 3

对象方法 date.strftime() 格式化打印为字符串:

import datetime

date = datetime.date.today()

print(date.strftime('今年是%Y年%m月%d日'))

运行程序:

python your_script.py
### 控制台将打印
# 今年是2025年03月26日
时间

datetime.time 数据类型的数据被称为 时间

import datetime

time1 = datetime.time(14)
time2 = datetime.time(14,30)
time3 = datetime.time(14,30,15)

print(time1)
print(time2)
print(time3)

运行程序:

python your_script.py
### 控制台将打印
# 14:00:00
# 14:30:00
# 14:30:15

对象方法 time.replace()time: strftime() 同上,不赘述。

datetime.datetime 数据类型的数据,中文 没有专业的称呼,含义同英语的 when,实例化方式同 datetime.datedatetime.time

类方法 datetime.datetime.now() 获取 当前日期与时间

import datetime

print(datetime.datetime.now())

运行程序:

python your_script.py
### 控制台将打印
# 2025-03-26 09:26:11.960411

类方法 datetime.datetime.strptime 根据格式化字符串返回一个 datetime 对象

import datetime

fs = '2008/2/28'

when = datetime.datetime.strptime(fs, '%Y/%m/%d')

print(when)

运行程序:

python your_script.py
### 控制台将打印
# 2008-02-28 00:00:00

对象方法 datetime.date()datetime.time() 获取日期与时间部分:

import datetime

now = datetime.datetime.now()

print(now.date())
print(now.time())

运行程序:

python your_script.py
### 控制台将打印
# 2025-03-26
# 09:30:33.358092

时间戳 是计算机中用于表示某一特定时间点的数值,表示从 1970年1月1日 00:00:00 UTC 开始到当前时间的总秒数

对象方法 datetime.timestamp() 获取 时间戳

import datetime

when = datetime.datetime(2008,1,1)

print(when.timestamp())

运行程序:

python your_script.py
### 控制台将打印
# 1199145600.0

对象方法 datetime.fromtimestamp()时间戳 转换为 datetime 对象

import datetime

ts = 114514.1919810

we = datetime.datetime.fromtimestamp(ts)

print(we)

运行程序:

python your_script.py
### 控制台将打印
# 1970-01-02 07:48:34.191981
时间段

datetime.timedelta 数据类型的数据被称为 时间段

import datetime

print(datetime.timedelta(hours = 5))

运行程序:

python your_script.py
### 控制台将打印
# 5:00:00
运算

时间段支持的运算:+-、数***、数/、比/%//
日期datetime对象 支持的运算:
+-**;
时间不能运算

常用指示符
符号说明
%Y四位年份
%m两位月份
%d两位日期
%H24小时制小时
%I12小时制小时
%M分钟
%S
%A星期全称
%a星期缩写
%B月份全称
%b月份缩写

标准库 - time

time 是干什么的

time 提供了各种 与程序时间相关 的函数。

time.perf_counter() 返回从程序开始到当前语句计时值

import time

print(start := time.perf_counter())
s = 0
for _ in range(1000):
    for x in range(10000):
        s += x
print(end := time.perf_counter())

print(end - start)

运行程序:

python your_script.py
### 控制台将打印
# 3986111.741345382
# 3986114.111161354
# 2.3698159721679986

time.sleep() 暂停程序指定秒数:

import time

print(start := time.perf_counter())
time.sleep(2.369)
print(end := time.perf_counter())

print(end - start)

运行程序:

python your_script.py
### 控制台将打印
# 3986306.487428033
# 3986308.860945891
# 2.3735178583301604

标准库 - math

数学常数
常数表示
math.piπ = 3.141592…
math.ee = 2.718281…
math.tauτ = 2π
math.inf+∞
math.nan非数字/奇异值
排列组合
函数表示
math.perm(n, k)排列数
math.comb(n, k)组合数
math.factorial(n)阶乘
数论
函数表示
math.gcd(*integers)最大公因数
math.lcm(*integers)最小公倍数
取整
函数表示
math.ceil(x)向上取整
math.floor(x)向下取整
极限
函数表示
math.isclose(a, b, rel_tol, abs_tol)是否相互逼近
math.isfinite(x)是否既不是无穷大也不是奇异值
math.isinf(x)是否为无穷大(正或者负)
math.isnan(x)是否为奇异值
对数
函数表示
math.log(x, base)x 的指定底数 (默认为 e) 的对数
math.log1p(x)ln(1 + x)
math.log2(x)x 的以 2 为底的对数
math.log10(x)x 的以 10 为底的对数
向量
函数表示
math.dist(p, q)距离
math.hypot(*coordinates)模长
角度制与弧度制
函数表示
math.degrees(x)弧度制转角度制
math.radians(x)角度制转弧度制
三角函数
函数表示
math.sin(x)正弦
math.cos(x)余弦
math.tan(x)正切
math.asin(x)反正弦
math.acos(x)反余弦
math.atan(x)反正切
math.atan2(y, x)arctan(y/x)
双曲函数
函数表示
math.sinh(x)双曲正弦
math.cosh(x)双曲余弦
math.tanh(x)双曲正切
math.asinh(x)反双曲正弦
math.acosh(x)反双曲余弦
math.atanh(x)反双曲正切
特殊函数
函数表示
math.erf(x)误差函数
math.erfc(x)误差补足函数
math.gamma(x)伽玛函数

标准库 - random

随机数生成

random.random() 生成 [0.0, 1.0) 之间的随机数值:

# 结果是伪随机的,但是你应该不一定能复现
import random

for _ in range(20):
    print(random.random())

运行程序:

python your_script.py
### 控制台将打印
# 0.9743731655003234
# 0.7020438139073761
# 0.26712472539226073
# 0.9552606668660204
# 0.25318269739640187
# 0.4970698291120971
# 0.36086880230153706
# 0.17567493710324344
# 0.7178408392576703
# 0.017451838610507364
# 0.0033670025509344725
# 0.25516149640714236
# 0.4995850983100787
# 0.21703926006707674
# 0.7143249577518763
# 0.3123073890846412
# 0.3452660438643701
# 0.5150106422864958
# 0.1518074993946592
# 0.5055346260553278
随机抽样

random.choices() 在序列中 有放回地抽取 k 个元素(可指定 权重):

# 结果是伪随机的,但是你应该不一定能复现
import random

seq = [1, 2, 3]
w = [1, 2, 3]
k = 10
print(random.choices(seq, weights=w, k=2))

运行程序:

python your_script.py
### 控制台将打印
# [2, 3]

random.sample 在序列中 不放回地抽取 k不重复 元素(不可指定权重):

# 结果是伪随机的,但是你应该不一定能复现
import random

your_list = [1, 1, 1, 0, 0]

print(random.sample(your_list, 3))

运行程序:

python your_script.py
### 控制台将打印
# [0, 1, 1]
洗牌/打乱

random.shuffle() 原地打乱列表顺序:

# 结果是伪随机的,但是你应该不一定能复现
import random

playing_cards = [
    'SpadesA', 'Spades1', 'Spades2', 'Spades3', 'Spades4', 'Spades5', 'Spades6', 'Spades7', 'Spades8', 'Spades9', 'Spades10', 'SpadesJ', 'SpadesQ', 'SpadesK',
    'HeartsA', 'Hearts1', 'Hearts2', 'Hearts3', 'Hearts4', 'Hearts5', 'Hearts6', 'Hearts7', 'Hearts8', 'Hearts9', 'Hearts10', 'HeartsJ', 'HeartsQ', 'HeartsK',
    'ClubsA', 'Clubs1', 'Clubs2', 'Clubs3', 'Clubs4', 'Clubs5', 'Clubs6', 'Clubs7', 'Clubs8', 'Clubs9', 'Clubs10', 'ClubsJ', 'ClubsQ', 'ClubsK',
    'DiamondsA', 'Diamonds1', 'Diamonds2', 'Diamonds3', 'Diamonds4', 'Diamonds5', 'Diamonds6', 'Diamonds7', 'Diamonds8', 'Diamonds9', 'Diamonds10', 'DiamondsJ', 'DiamondsQ', 'DiamondsK',
]

random.shuffle(playing_cards)

print(playing_cards)

运行程序:

python your_script.py
### 控制台将打印
# ['Diamonds6', 'Hearts8', 'Diamonds10', 'Hearts9', 'Spades8', 'SpadesA', 'HeartsJ', 'DiamondsA', 'SpadesJ', 'Clubs10', 'Diamonds2', 'ClubsA', 'ClubsK', 'Spades2', 'Clubs9', 'Hearts10', 'Spades3', 'Clubs2', 'ClubsQ', 'Clubs4', 'Diamonds8', 'HeartsK', 'Spades9', 'Spades1', 'Diamonds4', 'DiamondsQ', 'HeartsA', 'Clubs8', 'Diamonds7', 'Diamonds1', 'DiamondsJ', 'Spades7', 'Spades10', 'SpadesQ', 'Spades5', 'Hearts4', 'DiamondsK', 'Clubs7', 'Hearts6', 'ClubsJ', 'Clubs6', 'Hearts7', 'Diamonds5', 'Spades4', 'Hearts5', 'Clubs1', 'Hearts1', 'HeartsQ', 'Spades6', 'Clubs3', 'Hearts2', 'Hearts3', 'SpadesK', 'Diamonds3', 'Diamonds9', 'Clubs5']
正态分布

random.gauss() 生成服从正态分布的随机数:

import random

def is_gaussian(data, skew_threshold=1, kurtosis_threshold=1):
    n = len(data)
    mean = sum(data) / n
    variance = sum((x - mean) ** 2 for x in data) / (n-1)
    std_dev = variance ** 0.5
    
    skewness = (sum((x - mean) ** 3 for x in data) / n) / (std_dev ** 3)
    
    kurtosis = (sum((x - mean) ** 4 for x in data) / n) / (std_dev ** 4) - 3
    
    return abs(skewness) < skew_threshold and abs(kurtosis) < kurtosis_threshold

data = [random.gauss(0, 1) for _ in range(100)]

print(is_gaussian(data))

运行程序:

python your_script.py
### 控制台将打印
# True

标准库 - collections

collections 是干什么的

collections 的目的是为了给开发者提供 使用频繁实现机制较为简单数据结构。没有人会认同 重复造轮子的行为 ,特别是 让自己重复造轮子,既然实现步骤早已 烂熟于心,为什么不用 现成的 呢?

频次统计

collections.Counterdict 类的子类,它可以统计可迭代对象 每个元素出现的频次,并存储在 **字典(子类)**中:

import collections

print(collections.Counter([1, 1, 4, 5, 1, 4]))
print(collections.Counter("oiiaioiiai"))
print(collections.Counter({'a':2, 'b':3}))

运行程序:

python your_script.py
### 控制台将打印
# Counter({1: 3, 4: 2, 5: 1})
# Counter({'i': 6, 'o': 2, 'a': 2})
# Counter({'b': 3, 'a': 2})

对象方法 counter.most_common() 返回出现频次最高的前n个元素

以朱自清的《背影》为例:

我与父亲不相见已二年余了,我最不能忘记的是他的背影。
那年冬天,祖母死了,父亲的差使也交卸了,正是祸不单行的日子。我从北京到徐州,打算跟着父亲奔丧回家。到徐州见着父亲,看见满院狼藉的东西,又想起祖母,不禁簌簌地流下眼泪。父亲说:“事已如此,不必难过,好在天无绝人之路!”
回家变卖典质,父亲还了亏空;又借钱办了丧事。这些日子,家中光景很是惨澹,一半为了丧事,一半为了父亲赋闲。丧事完毕,父亲要到南京谋事,我也要回北京念书,我们便同行。
到南京时,有朋友约去游逛,勾留了一日;第二日上午便须渡江到浦口,下午上车北去。父亲因为事忙,本已说定不送我,叫旅馆里一个熟识的茶房陪我同去。他再三嘱咐茶房,甚是仔细。但他终于不放心,怕茶房不妥帖;颇踌躇了一会。其实我那年已二十岁,北京已来往过两三次,是没有什么要紧的了。他踌躇了一会,终于决定还是自己送我去。我再三劝他不必去;他只说:“不要紧,他们去不好!”
我们过了江,进了车站。我买票,他忙着照看行李。行李太多了,得向脚夫行些小费才可过去。他便又忙着和他们讲价钱。我那时真是聪明过分,总觉他说话不大漂亮,非自己插嘴不可,但他终于讲定了价钱;就送我上车。他给我拣定了靠车门的一张椅子;我将他给我做的紫毛大衣铺好座位。他嘱我路上小心,夜里要警醒些,不要受凉。又嘱托茶房好好照应我。我心里暗笑他的迂;他们只认得钱,托他们只是白托!而且我这样大年纪的人,难道还不能料理自己么?我现在想想,我那时真是太聪明了。
我说道:“爸爸,你走吧。”他往车外看了看,说:“我买几个橘子去。你就在此地,不要走动。”我看那边月台的栅栏外有几个卖东西的等着顾客。走到那边月台,须穿过铁道,须跳下去又爬上去。父亲是一个胖子,走过去自然要费事些。我本来要去的,他不肯,只好让他去。我看见他戴着黑布小帽,穿着黑布大马褂,深青布棉袍,蹒跚地走到铁道边,慢慢探身下去,尚不大难。可是他穿过铁道,要爬上那边月台,就不容易了。他用两手攀着上面,两脚再向上缩;他肥胖的身子向左微倾,显出努力的样子。这时我看见他的背影,我的泪很快地流下来了。我赶紧拭干了泪。怕他看见,也怕别人看见。我再向外看时,他已抱了朱红的橘子往回走了。过铁道时,他先将橘子散放在地上,自己慢慢爬下,再抱起橘子走。到这边时,我赶紧去搀他。他和我走到车上,将橘子一股脑儿放在我的皮大衣上。于是扑扑衣上的泥土,心里很轻松似的。过一会儿说:“我走了,到那边来信!”我望着他走出去。他走了几步,回过头看见我,说:“进去吧,里边没人。”等他的背影混入来来往往的人里,再找不着了,我便进来坐下,我的眼泪又来了。
近几年来,父亲和我都是东奔西走,家中光景是一日不如一日。他少年出外谋生,独力支持,做了许多大事。哪知老境却如此颓唐!他触目伤怀,自然情不能自已。情郁于中,自然要发之于外;家庭琐屑便往往触他之怒。他待我渐渐不同往日。但最近两年不见,他终于忘却我的不好,只是惦记着我,惦记着我的儿子。我北来后,他写了一信给我,信中说道:“我身体平安,惟膀子疼痛厉害,举箸提笔,诸多不便,大约大去之期⒂不远矣。”我读到此处,在晶莹的泪光中,又看见那肥胖的、青布棉袍黑布马褂的背影。唉!我不知何时再能与他相见!

统计词频:

import collections

with open('《背影(朱自清)》.txt', encoding = 'utf-8') as f:
    counter = collections.Counter(f.read())

counter.most_common(20)

运行程序:

python your_script.py
### 控制台将打印
# [(',', 89), ('。', 51), ('我', 50), ('他', 41), ('的', 30), ('了', 29), ('不', 28), ('去', 18), ('是', 15), ('子', 13), ('一', 13), ('着', 12), ('看', 12), ('上', 12), ('走', 12), ('父', 11), ('亲', 11), ('见', 11), ('到', 11), ('过', 11)]
双端队列

在此后的教程里,你必定会接触到 队列 两种数据结构,它们将 十分频繁地 进出你的 视野。并非说手动实现不好,然而 一次两次可以几百上千道算法题会压垮你,你会发现次次手动实现过于耗费头发,所以,既然Python都 把菜喂到你嘴里了,你就接受它的好意吧。

collections.deque 并不继承 list 类,而是直接继承 object 类:

object
deque
maxlen
append()
appendleft()
pop()
popleft()
clear()
copy()
list
append()
clear()
copy()
extend()
index()
insert()
pop()
remove()

与 list 同名的对象方法操作都是一样的,不再赘述。

对象方法 deque.appendleft() 在双端队列 头部添加 元素:

import collections

dq = collections.deque()
print(dq)
for x in range(10):
    dq.appendleft(x)
    print(dq)

运行程序:

python your_sricpt.py
### 控制台将打印
# deque([])
# deque([0])
# deque([1, 0])
# deque([2, 1, 0])
# deque([3, 2, 1, 0])
# deque([4, 3, 2, 1, 0])
# deque([5, 4, 3, 2, 1, 0])
# deque([6, 5, 4, 3, 2, 1, 0])
# deque([7, 6, 5, 4, 3, 2, 1, 0])
# deque([8, 7, 6, 5, 4, 3, 2, 1, 0])
# deque([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

对象方法 deque.popleft 在双端队列 头部移除 元素:

import collections

dq = collections.deque()
for x in range(10):
    dq.appendleft(x)

for _ in range(10):
    print(dq)
    dq.popleft()
print(dq)

运行程序:

python your_script.py
### 控制台将打印
# deque([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
# deque([8, 7, 6, 5, 4, 3, 2, 1, 0])
# deque([7, 6, 5, 4, 3, 2, 1, 0])
# deque([6, 5, 4, 3, 2, 1, 0])
# deque([5, 4, 3, 2, 1, 0])
# deque([4, 3, 2, 1, 0])
# deque([3, 2, 1, 0])
# deque([2, 1, 0])
# deque([1, 0])
# deque([0])
# deque([])
双端队列 - 滑动窗口

考虑以下问题:

求长度为 k 的子数值数据组的最大平均值

解法1

import math
import time

def max_average_brute(nums, k):
      max_avg = -math.inf
      for i in range(len(nums) - k + 1):
          current_sum = sum(nums[i:i+k])
          max_avg = max(max_avg, current_sum / k)
      return max_avg 

start = time.perf_counter()
max_average_brute(range(1000000), 100)
end = time.perf_counter()
print(end - start)

运行程序:

python your_script.py
### 控制台将打印
# 2.716648146044463

双端队列 的对象属性 deque.maxlen 可以简化工作。

解法2

import collections
import time

def max_average_brute(nums, k):
      dp = collections.deque(nums[:k], maxlen = k)
      window_sum = sum(dp)
      max_sum = window_sum
      for n in nums[k:]:
          window_sum += n - dp[0]
          dp.append(n)
          max_sum = max(max_sum, window_sum)
      return max_sum / k

start = time.perf_counter()
max_average_brute(range(1000000), 100)
end = time.perf_counter()
print(end - start)

运行程序:

python your_script.py
### 控制台将打印
# 0.33703770488500595

标准库 - itertools

itertools 是干什么的

在学习过 迭代器生成器 后,经常需要处理固定种类的 迭代器,比如有限迭代器无限生成器等。Python归纳总结了常用的迭代器,将它们 集中起来提供给开发者们itertools 就是这样诞生的,接下来看Python给你准备了哪些 常用迭代器

无限迭代器

itertools.count 是一种 无限迭代器类,它实例化所有具有 等差性质无限迭代器

import itertools

a3_d4 = itertools.count(3, 4)
am1_d2 = itertools.count(-1, 2)
a10_dm3 = itertools.count(10, -3)
print([next(a3_d4) for _ in range(10)])
print([next(am1_d2) for _ in range(10)])
print([next(a10_dm3) for _ in range(10)])

运行程序:

python your_script.py
# [3, 7, 11, 15, 19, 23, 27, 31, 35, 39]
# [-1, 1, 3, 5, 7, 9, 11, 13, 15, 17]
# [10, 7, 4, 1, -2, -5, -8, -11, -14, -17]

itertools.cycle是一种 无限迭代器类,它实例化所有具有 周期性质 的无限迭代器:

import itertools

iadd = lambda x: x + 1
lshift = lambda x: x * 2

cycle_il = itertools.cycle([iadd, lshift])

cycle_func = lambda x: next(cycle_il)(x)

a0_d1 = itertools.count(0, 1)
gen = map(cycle_func, a0_d1)
print([next(gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [1, 2, 3, 6, 5, 10, 7, 14, 9, 18]
迭代器处理 - 预处理

itertools.dropwhile() 排掉 满足条件 的元素,直到 不满足条件

import itertools

def drop(dfunc):
    df = itertools.dropwhile
    def inp(data):
        return df(dfunc, data)
    return inp

nag = lambda x: x < 0
even = lambda x: x % 2 == 0

natn = itertools.count(0, 1)
stag = itertools.cycle([1, -1, 1])
gen = (x * y for x, y in zip(natn, stag))
rsgen = drop(nag)(drop(even)(gen))

print([next(rsgen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [2, 3, -4, 5, 6, -7, 8, 9, -10, 11]

itertools.takewhile()itertools.dropwhile() 逻辑相反,不再赘述。

迭代器处理 - 重复

itertools.repeat() 返回重复对象自身指定次数的迭代器,若不指定重复次数,将返回无限迭代器:

import itertools

it1 = itertools.repeat([1, 2, 3], 5)
it2 = itertools.repeat(-1)
print([next(it1) for _ in range(5)])
print([next(it2) for _ in range(5)])

运行程序:

python your_script.py
### 控制台将打印
# [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
# [-1, -1, -1, -1, -1]
迭代器处理 - 拼接

itertools.chain() 将有限个可迭代对象首尾拼接,返回拼接后的迭代器:

import itertools

it = itertools.chain(range(5), range(4, -1, -1))

print([next(it) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [0, 1, 2, 3, 4, 4, 3, 2, 1, 0]
迭代器处理 - 切片

itertools.islice()迭代器 进行 切片同内置序列的切片行为,不再赘述。

迭代器处理 - 复制

itertools.tee() 复制迭代器为 n个独立的迭代器

import itertools
import collections

gen = (x for x in range(10))
gens = itertools.tee(gen, 10)
gs = collections.deque(maxlen = 10)
m = [[] for _ in range(10)]

for g in gens:
    gs.appendleft(g)
    for mi, gi in zip(m, gs):
        mi.append(next(gi))

print(m)

运行程序:

python your_script.py
### 控制台将打印
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# [1, 1, 1, 1, 1, 1, 1, 1, 1]
# [2, 2, 2, 2, 2, 2, 2, 2]
# [3, 3, 3, 3, 3, 3, 3]
# [4, 4, 4, 4, 4, 4]
# [5, 5, 5, 5, 5]
# [6, 6, 6, 6]
# [7, 7, 7]
# [8, 8]
# [9]
迭代器处理 - 分组

itertools.groupby()连续且具有相同键值的的元素 分组。

以以下 csv文件(your_sales.csv) 为例,按季度分组

序号,年份,季度,销售额
0,2001,1,10017.5
1,2001,2,10129.8
2,2001,3,10165.1
3,2001,4,10301.3
4,2002,1,10305.2
5,2002,2,10373.1
6,2002,3,10498.7
7,2002,4,10601.9
8,2003,1,10701.7
9,2003,2,10766.9
10,2003,3,10887.4
11,2003,4,11011.6

编写脚本:

import csv
import itertools

with open('your_sales.csv', newline = '', encoding = 'utf-8'):
    reader = csv.reader(f)
    next(reader)
    key = lambda row: int(row[2])
    sd = sorted(reader, key = key)

gb = itertools.groupby
for q, g in gb(sd):
    print(f'季度: {q}')
    for r in g:
        print(f'    年份: {r[1]}, 销售额: {r[3]}')

运行程序:

python your_script.py
### 控制台将打印
# 季度: 1
#     年份: 2001, 销售额: 10017.5
#     年份: 2002, 销售额: 10305.2
#     年份: 2003, 销售额: 10701.7
# 季度: 2
#     年份: 2001, 销售额: 10129.8
#     年份: 2002, 销售额: 10373.1
#     年份: 2003, 销售额: 10766.9
# 季度: 3
#     年份: 2001, 销售额: 10165.1
#     年份: 2002, 销售额: 10498.7
#     年份: 2003, 销售额: 10887.4
# 季度: 4
#     年份: 2001, 销售额: 10301.3
#     年份: 2002, 销售额: 10601.9
#     年份: 2003, 销售额: 11011.6

  1. 有时候用制表符分隔,此时称为tsv ↩︎

  2. 不能有间隔 ↩︎

  3. 每行字段数应与标题行一致,否则解析错误 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值