Python Standard Libary

官方文档:https://docs.python.org/3/library/index.html

logging

基本的用法,日志信息打印在终端并且同时保存在文件中(运行程序的过程中文件内容会不断增加,不是运行完后一次性写入)

import logging
logname = "xx.py"
filename = "x.log"
logger = logging.getLogger(logname)  # logname为可选参数

fh = logging.FileHandler(filename, mode="w")
fh.setFormatter(logging.Formatter(fmt="%(asctime)s %(filename): %(levelname): %(message)s"))
fh.setLevel(logging.INFO)
logger.addHandler(fh)

ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
ch.setFormatter(fmt="%(asctime)s: %(message)s")
logger.addHandler(ch)

logger.setLevel(logging.INFO)
logger.info("xxx")

控制输出内容,不同的日志文件写不同的内容

备注:要产生不同的logger,要传递不同的logger_name,例如如下情况得到的两个logger会是一样的:

argparse

备注:parse_args 函数存在 prefix-match的特性, 具体可参考官方文档的如下例子:

有些情况下,可以只解析一部分的命令行参数,而其余参数用其他逻辑进行处理,此时可以使用 parse_known_args 函数。备注:parse_known_args 函数也适用于 prefix-match 规则。

许多工具会包含许多子命令, 例如: git add, git commit, git push, 其中: add/commit/push 都是子命令, argparse 包也能做到这一点:

os

io

open 函数的 mode 参数: r+b 表示可读可写(但只要调用了write, 原本的数据就会被覆盖掉)

read

peek

seek

collections

defaultdict

递归定义 defaultdict: 参考 (stackoverflow)[https://stackoverflow.com/questions/20428636/how-to-convert-defaultdict-to-dict]

OrderedDict

typing【涉及到generic看不懂】

typing 模块用于注解, 这些注解在运行时不起作用, 只适用于 mypy 工具做静态检查, 也方便读者阅读

看不懂的地方大概有这么几处:

  • typing.TypeVar, typing.NewType

  • typing.Generic

  • python type 关键字

Tuple

  • Tuple:元组类型

  • Tuple[int, str]:第一个元素为整数,第二个元素类型为字符串

  • Tuple[int, ...]:若干个整数

  • Tuple[Any, ...]:等价于 Tuple

Optional

Optional[Sized] 等同于 Union[Sized, None]

Sized

Sized 表示有一个具有 __len__ 方法的对象,

Callable

  • Callable[[int], str]:输入是 int 类型,输出是 str 类型的函数

  • Callable[..., str]:输出是 str 类型的函数,对输入不加约束

TypeAlias, NewType, TypeVar

TypeAlias

TypeVar 比较常见, 官方文档 解释如下

The preferred way to construct a type variable is via the dedicated syntax for generic functions, generic classes, and generic type aliases

NewType

在运行时, 基本上 x = NewType("UserID", int)(x), Python 3.8 中对 NewType 实现源码 如下:

__name__ 属性探索:

TypeVarNewType 涉及的 name 参数是字符串类型, 影响的是 __name__ 属性

Generic

类似这个写法 List[int], 这种方括号的写法看起来似乎是 C++ 里的泛型(模板)一样, 我们也可以自定义 (例子参考这篇博客)

继承 Generic, 就可以在类里面使用“模板类型”

还存在一个疑问:

这个例子有些看不懂, 起源是 openai==1.1.1 python 包:

结果是

于是写了个测试例子

输出:

overload

Protocol

python 3.8 新特性, 相当于 Java 的 Interface

typing.cast

参考博客

IO

enum

枚举类型

dataclasses (python>=3.7 才可用)

引用官方文档的说明, 这个模块主要提供了一个针对类的装饰器 dataclass,以自动生成一些特殊函数,例如:__init__, __repr__, __eq__ 等(对应于 C 语言中的数据结构,即没有实例函数)

This module provides a decorator and functions for automatically adding generated special methods such as init() and repr() to user-defined classes

主要的接口为:

  • dataclasses.dataclass

  • dataclasses.field

dataclasses.dataclass 的简单用法如下:

备注:dataclass 还会自动生成 __eq__ 等函数, 也可以设定 eq=False 抑制这一行为

field 的简单用法如下:

更为深入的细节查阅官方文档:

  • 如果 dataclass 装饰的类发生继承关系时, 自动生成的 __init__ 函数的参数顺序一般来说是先父类, 再子类。但还有许多微妙之处

  • dataclass 中的一些 field 仅作为关键字参数如何处理

unicodedata

unicode 编码的目的是攘括所有的字符,然而它本身也有版本号, python 的每个版本所支持的 unicode 版本号也不相同, 例如:

unicode 的完整列表可以参考 wiki,unicode 的字符范围为:0-0x10FFFF,因此最多能容纳1114112个码位, 大多数字符的编码范围在 0-0xFFFF (最多65536个)之间。一些例子如下:

  • U+0025: %, name 为 PERCENT SIGN

  • U+0B90: , name 为 TAMIL LETTER AI

而在编码界,需要区分编码方式实现方式,上面所讲的 Unicode 属于编码方式的范畴,即规定了字符集。而从实现方式的角度,需要将每个字符映射为一个具体的二进制表示。一个自然的方式是将所有的字符按照 Unicode 的定义方式表示为 6 位 16 进制数,但实际上为了省空间,普遍采用的 Unicode 实现方式utf-8utf-16 等,其中最通用的是 utf-8,而 utf-8 具体的字符与字节的对应关系此处不再做展开。

这里简要举一些 unicodedata 的使用例子,更多复杂的内容请参考维基

备注:

  • 字符类别参见官网,例如:类别 "Zs" 表示 Space Seperator, 例如空格;但制表符的类别为 "Cc" Control;而中文字符以及很多其他字符被归为 Lo Other Letter。

  • 字符 normalize,有几种转换方式:NFC、NFD、NFKC、NFKD。例如有些字符有多种表示:U+00C7 (LATIN CAPITAL LETTER C WITH CEDILLA,形状为字母C下面有个类似逗号的符号) 也可以被表示为 U+0043 (LATIN CAPITAL LETTER C,字母C) U+0327 (COMBINING CEDILLA,类似于一个逗号的符号).

subprocess (待补充)

  • subprocess 模块的作用是运行命令行可以执行的命令

  • multiprocessing 模块的典型用法是用多进程执行同一个python 代码

subprocess.run

官方文档

其中shell参数的默认值为,shell=True表示"命令"(可能用词不准确)在shell中执行, 文档中说除非必要, 否则不要设置为True. 注意: 在window下, 上述情况需设置为True, 主要原因是windows下echo不是一个可执行文件, 而是cmd中的一个命令.

科普链接

  • 一个在windows环境变量PATH目录下的可执行文件(以.exe结尾), 可以通过win+R组合键后敲入文件名进行执行; 而echo在windows下不是一个自带的可执行文件, 而是cmd窗口中的一个内置命令.

  • windows下cmd是一个shell, 而平时所说的dos是一种操作系统的名字, 而dos命令是这个操作系统中的命令. cmd窗口下的能执行的命令与dos命令有许多重叠之处, 但不能混为一谈.

  • 所谓shell, 这是一个操作系统中的概念, 不同的操作系统有不同的shell, 常见的有: windows下的cmd(命令行shell), powershell(命令行shell), windows terminal(命令行shell), 文件资源管理器(图形化shell); linux下的bash(命令行shell, 全称: Bourne Again shell), shell是一种脚本语言.

subprocess.check_output

用于执行脚本得到输出结果

multiprocessing(待补充)

multiprocessing.Process

此为一个相对底层的 API,用于直接创建子进程运行 python 代码。

用法一:使用 multiprocessing.Process创建进程并传入需要运行的 python 函数及实参

方法二:自己写一个类继承 multiprocessing.Process,示例如下:

Pool

multiprocessing.Pool主要有apply, apply_async, close, imap, imap_unordered, join, map, map_async, starmap, starmap_async, terminate

close, join, terminate

close指关闭进程池, 即不再往里面添加新的任务

join指等待进程池内的所有进程完成任务

terminate指立刻中断所有进程的执行

map, map_async, starmap, starmap_async

首先, python中

map只接收单参数的函数, starmap是接受多个参数的版本. 这四个函数实际上都调用了_map_async, 具体参见源码, map会阻塞主进程的执行, 但子进程是并行化的. 在python官方文档中提到, 对于序列很长的时候, 可以使用imap并指定chunksize参数, 能极大提升效率

map, apply, imap, imap_unodered

multi-args
Concurrence
Blocking
Ordered-results

map

no

yes

yes

yes

map_async

no

yes

no

yes

apply

yes

no

yes

no

apply_async

yes

yes

no

no

concurrence指的是子进程之间能否并发执行, blocking指的是是否阻塞主进程的执行.

最常用的是map, 非特殊情况其他不要尝试, 简单的示例如下, 但推荐使用with语句

Queue、Pipe

其他一些与进程, cpu相关的测试代码:

concurrent.futures

concurrent.futures的主要作用是创建异步的线程/进程池。主要的类为 concurrent.futures.Excutor(异步调用), ThreadPoolExecutor(异步线程池), ProcessPoolExecutor(异步进程池)

concurrent.futures vs multiprocessing.Pool(待理解)

并发执行

python 中涉及关于并发执行的包及主要的类/方法有如下

  • threading: threading.Thread, threading.Pool, threading.Queue

  • multiprocessing: multiprocessing.Process, multiprocessing.Pool, multiprocessing.Queue

  • concurrent: concurrent.futures.Excutor, concurrent.futures.ThreadPoolExecutor, concurrent.futures.ProcessPoolExecutor

  • subprocess: subprocess.call

  • queue: queue.Queue

此处有多个疑问:

  • concurrent.futures.ProcessPoolExecutormultiprocessing.Pool 之间的区别(待补充)

  • queue.Queuemultiprocessing.Queue 之间的区别(待补充)

例子1: 使用concurrent.futures完成子任务异步调用

例子2: 多个进程修改变量

re、glob

此部分微妙处比较多, 许多符号例如?有着多种含义, 需仔细校对

pattern的写法

参考

示例里pattern的首位两个正斜杠不是正则表达式的一部分, 示例中所谓的匹配实际上对应的是re.search方法, 意思是存在子串能符合所定义的模式, 以下是pythonre模块在单行贪婪模式, 更细节的内容参见后面.

写法
含义
备注
示例

^

匹配开头

/^abc/可以匹配abcd, 但不能匹配dabc

$

匹配结尾

/abd$/可以匹配cabc, 但不能匹配abcc

.

匹配任意单个字符

单行模式下不能匹配

/a.v/可以匹配acv, 但不可以匹配av

[...]

匹配中括号中任意一个字符

大多数特殊字符^, ., *, (, {均无需使用反斜杠转义, 但存在例外(其实还不少), 例如: [\需要用反斜杠转义

/[.\["]/表示匹配如下三个字符: ., [, "

[^...]

匹配不在中括号中的任意一个字符

*

匹配任意个字符

{m,n}

匹配前一个字符[m,n]

+

匹配前一个字符至少1次

?

匹配前一个字符一次或零次

`

`

备注:

  • 正则表达式中特有的符号.*+[]^$()|{}?, 如果需要表达它们自身, 一般需要转义. 另外, 有以下几个常用的转义字符:

    转义写法
    备注

    \b

    匹配数字/下划线/字母(沿用计算机语言中_word_的概念)

    \B

  • 贪婪匹配与非贪婪匹配

    *, +, ?, {m, n}, {m,}, {,n}均为贪婪模式, 表示尽量匹配更长的字符串, 例如/t*/匹配ttT中的tt. 相反地, 在上述符号后添加?后变为非贪婪模式, 表示匹配尽量短的匹配字符串. 有一个特别的例子如下:

  • [...]形式的pattern存在较多特殊情况

    • /[tT]*/可以匹配tTt, 即不需要是重复地若干个t或者若干个T. 类似地/[tT]{2}/也可以匹配tT.

    • 如果需要匹配\, 则需要使用反斜杠将其转义, 例如: /[\\a]/表示匹配\或者a.

    • /[a-z]/这种表示是前闭后闭区间(首尾的两个字符都含在匹配范围内), 并且必须按照Unicode的顺序前小后大(见后文).

    • []不需要进行转义, 如果需要匹配]那么]必须放在开头, 例如/[]a]/表示匹配]或者a. 但当采用非的语义时, ]应该放在^后面的第一个位置, 例如/[^]a]/表示不匹配]a.

    • 如果需要匹配-, 可以将-放在开头或结尾, 或者是用\-进行转义.

    • -]混合的情形, 例子: /[-]]/表示匹配-], /[]-]/表示匹配]或者-.

    • ^字符无需进行转义, 如果需要匹配^, 那么要避免将^放在第一个位置即可. 例如: /[1^]/表示匹配1或者^.

    • 还有更多规则...

  • [], {}, ()的嵌套问题, 根据目前所知, 一般不能多层嵌套, 具体地, ()[]{}可以正常使用, 这是因为圆括号只是标记一个范围, 而[]{}内部不能嵌套三种括号. 例如: /([tT]he)//([tT]{2})/是合法的pattern.

  • /\1/这种以反斜杠开头加上数字的表示法表示匹配与上一个圆括号块完全相同的字符串, 标号从1开始, (?:)这种写法用于取消当前块的标号

  • (?=), (?!)被称为Lookahead Assertions, 表示匹配字符但不消耗, 例子/[A-Za-z]{2}(?!c)/可以匹配bag, 最终获取到ba

    (?<=), (?<!)与上两者类似, 但匹配的是前面的字符

常用函数

更多说明

贪婪/非贪婪, 单行/多行匹配模式

正则表达式也可以用于字节流的匹配

一些骚操作

python raw string: python中的raw string不能以奇数个反斜杠作为结尾, 例如x=r'\'这种语句解释器会直接报错, 但x=r'\\'会被解释器正确地解释为两个反斜杠. 其原因可以参见这个问答, 简述如下: 在python中对raw string的解释是碰到\就将\与下一个字符解释为本来的含义, 也就是说raw string出现了反斜杠, 后面必须跟着一个字符, 所以r'\'中的第二个单引号会被认为是一个字符, 但这样一来就没有表示字符串结尾的单引号了.

Unicode与UTF-8的关系: 参考阮一峰博客, 简单来说, Unicode是一个字符代码, 用整数(目前至多为21bit)来表示所有的字符对应关系, 而UTF-8是一种具体的字符编码. 前者的重点在于将全世界的字符都有一个唯一的对应关系, 全球公认, 而后者的重点在于在保证能编码所有Unicode中规定地字符集的前提下, 利用更巧妙地方式对这种对应关系进行存储, 方便传输.

样例

python re模块的实现

使用一个东西, 却不明白它的道理, 不高明

linux下的通配符(glob)

参考资料: 阮一峰博客

通配符又叫做 globbing patterns。因为 Unix 早期有一个/etc/glob文件保存通配符模板,后来 Bash 内置了这个功能,但是这个名字被保留了下来。通配符早于正则表达式出现,可以看作是原始的正则表达式。它的功能没有正则那么强大灵活,但是胜在简单和方便。

?表示单个字符, *代表任意数量个字符, [abc]表示方括号内任意一个字符, [a-z]表示一个连续的范围, [^abc][!abc][^a-c][!a-c]表示排除方括号内的字符

{abc,def}表示多字符版本的方括号, 匹配任意abcdef, 中间用逗号隔开, 大括号可以嵌套, 例如{j{p,pe}g}表示jpgjpeg.

{11..13}表示111213, 但如果无法解释时, 例如: {1a..1c}则模式会原样保留.

注意点: *不能匹配/, 所以经常会出现a/*.pdf这种写法

ctypes

ctypes的代码运行效率不如cython

1. C/C++程序的编译

2. 使用ctypes

参考博客: Python - using C and C++ libraries with ctypes | Solarian Programmer

以下仅为tutorial(可能理解不准确)

2.1 调用动态链接库

以一个例子加以说明: 来源

解释: 由于python与c两种语言在数据结构上有着明显的不同, 因此利用ctypes调用C代码时需要进行相应的类型转换: 以上述的add_float为例, python中浮点数都是双精度的(不妨记为Python-double). 而adder.c函数参数都是单精度的, 不妨记为C-float, 可以将调用过程细化为几步

  • python数据类型转换为c数据类型: p_a -> c_a, p_b-> c_b

  • c代码调用并返回

  • python将c代码返回的结果转换为python类型

为了使用add_float(1.0, 2.0)这种形式进行调用, 必须将1.0转换为适应c的数据形式(add_float.argtypes), 对于返回值, 同样道理, 也应指定返回时c->python的转换(add_float.restype)

经过一番探索后发现, 最好还是指定argtypesrestype, 前者也许可以不指定, 后者必须指定, 并且不要试图理解不指定restype时的结果, 大概是implement dependent的东西, 似乎也不能直接用IEEE 754浮点数表示法进行解释. 大约是: 不指定restype时默认是c_int, 深入到底层时, 应该是C端传回了一些字节, Python端将其解读为int, 但不能用同样的比特流解读为浮点型? (不要深究: ctypes用了cpython安装时的一些东西, 注意: cpython是python的一种实现, 也是最普遍的实现, 与cython是不同的东西)

argtypes与restype的问题参见: 链接1, 链接2.

2.2 类似于cython的方式

ctypes也支持直接用python代码写C代码? 但效率不如cython

3. ctypes官方文档的学习记录

3.1 python类型与c类型的转换

None, int, bytes, (unicode) strings是python中能直接作为C函数参数的数据类型, 其中None代表C中的空指针, bytes与strings作为char *wchar_t *的指针, int作为C中的int类型, 但注意过大的数字传入C时会被截断. 也就是说上面的restypeargtypes可以不指定仅限于上述几种数据类型.

None, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls. None is passed as a C NULL pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (char * or wchar_t *). Python integers are passed as the platforms default C int type, their value is masked to fit into the C type.

inspect

inspect.signature

返回函数的特征标(即原型或者说是参数名列表)

inspect.stack

用于返回当前的函数调用栈

inspect.isclass(obj)

用于判断 obj 是否为一个类

输出一个实例的完整类名

备注:__name__ vs __qualname__: stackoverflow

inspect.unwrap 用于解除用 functools.wraps 修饰的装饰器

weakref

json

asyncio

概念澄清:

  • Asynchronous IO (async IO): 是一个语言无关的模型

  • async/await: 是 Python 的关键字

  • asyncio: 是 Python 的一个标准库

  • coroutine: 在 Python 中是一类特殊的 generator 函数

  • 不是太确定: 在讨论协程时, 普通函数也被称为阻塞型函数 (blocking function), 而使用 async def 定义的函数被称为 coroutine, 也被称为非阻塞型函数 (nonblocking function)

使用示例 (八股文?)

asyncio.run(afn()) vs asyncio.get_event_loop().run_until_complete(afn())

两者基本等价? (不确定)

源码如下:

疑问:

  • asyncio.get_event_loop() vs asyncio.get_running_loop(): 前者通常是在非协程环境中使用, 例如在模块级别. 后者通常是在 async def 的函数体内被使用

  • run_in_executor() vs run_until_complete(): 前者接收的是普通函数, 后者接收的是 async def 的函数

  • return await 是在做什么? 仅仅是省一行代码

await vs create_task (OK)

首先观察上面代码的几个变体观察执行行为:

create_task 会自动加入事件循环(也就是会择机执行), 但它却不会被等待执行完成, 另外如果这个 task 不被显式地 await, 事件循环结束前也不会等待这些 task 完成, 也就是:

Future and run_in_executor (OK)

Task 是 Future 的子类, 一般用的多的是 await, 偶尔需要用到 task, 比较少直接用 future.

下面的代码的主要应用场景是: 假设有一段耗时的同步函数代码, 我希望让它不阻塞我的事件循环, 并且在"后台"执行, 直到某个时间点我需要确认其执行完才能执行后面的逻辑

而这种场景已有官方的解决方案: run_in_executor

疑问(但基本上可以确认, 只是实现细节不清楚): run_in_executor 的底层实现是基于 Future 的

select/selector

select 似乎是对系统调用的直接暴露, 以下代码段暂时不知道在做啥 https://chatgpt.com/share/2ee353c0-8043-4dd5-a8f4-a7c9504310fd

常见的 epoll 事件类型有:

select.EPOLLIN:对应的值是1,表示文件描述符可读,即有数据可以读取(例如客户端发送了数据,或者有新连接请求)。 select.EPOLLOUT:对应的值是4,表示文件描述符可写,即可以向其发送数据而不会阻塞。 select.EPOLLERR:对应的值是8,表示文件描述符上发生了错误。 select.EPOLLHUP:对应的值是16,表示文件描述符被挂起或连接关闭。 select.EPOLLET:对应的值是2的31次方,用于启用边沿触发模式(默认是水平触发)。

服务端

客户端

Last updated

Was this helpful?