Files
rikako-note/python/py.md
2025-08-21 02:20:25 +08:00

2356 lines
83 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [Python](#python)
- [变量](#变量)
- [字符串](#字符串)
- [字符串首字母大写](#字符串首字母大写)
- [字符串全部字符大写](#字符串全部字符大写)
- [字符串全部字符小写](#字符串全部字符小写)
- [字符串删除空白符](#字符串删除空白符)
- [访问字符串中字符](#访问字符串中字符)
- [字符串切片](#字符串切片)
- [字符串迭代](#字符串迭代)
- [数字](#数字)
- [/](#)
- [//](#-1)
- [数字类型向字符串类型转换](#数字类型向字符串类型转换)
- [列表](#列表)
- [访问列表中元素](#访问列表中元素)
- [列表元素操作](#列表元素操作)
- [修改](#修改)
- [插入到末尾](#插入到末尾)
- [在某位置之前插入](#在某位置之前插入)
- [删除列表中的元素](#删除列表中的元素)
- [列表与栈api](#列表与栈api)
- [remove](#remove)
- [列表排序](#列表排序)
- [sort](#sort)
- [sorted](#sorted)
- [列表中顺序反转](#列表中顺序反转)
- [获取列表长度](#获取列表长度)
- [遍历列表](#遍历列表)
- [range](#range)
- [max, min, sum](#max-min-sum)
- [根据一个列表生成另一个列表](#根据一个列表生成另一个列表)
- [切片](#切片)
- [列表复制](#列表复制)
- [元组](#元组)
- [if](#if)
- [and/or](#andor)
- [列表中是否包含某值](#列表中是否包含某值)
- [列表中是否不包含某值](#列表中是否不包含某值)
- [多分支if/elif/else](#多分支ifelifelse)
- [字典](#字典)
- [向字典中添加键值对](#向字典中添加键值对)
- [删除字典中的键值对](#删除字典中的键值对)
- [字典遍历](#字典遍历)
- [按顺序遍历字典的key](#按顺序遍历字典的key)
- [while](#while)
- [函数](#函数)
- [参数默认值](#参数默认值)
- [接收多个参数](#接收多个参数)
- [文件操作](#文件操作)
- [文件读取](#文件读取)
- [写入文件](#写入文件)
- [文件末尾追加](#文件末尾追加)
- [module](#module)
- [运行module](#运行module)
- [module search path](#module-search-path)
- [dir](#dir)
- [package](#package)
- [from ... import \*](#from--import-)
- [Errors and Exceptions](#errors-and-exceptions)
- [Syntax Errors](#syntax-errors)
- [Exceptions](#exceptions)
- [handling exceptions](#handling-exceptions)
- [python异常结构](#python异常结构)
- [rasing exception](#rasing-exception)
- [Exception Chaining](#exception-chaining)
- [用户自定义异常类型](#用户自定义异常类型)
- [define cleanup action](#define-cleanup-action)
- [predefined clean-up action](#predefined-clean-up-action)
- [rasing multi exceptions](#rasing-multi-exceptions)
- [except\*](#except)
- [enriching exceptions with notes](#enriching-exceptions-with-notes)
- [多线程](#多线程)
- [linux命令交互](#linux命令交互)
- [正则](#正则)
- [match](#match)
- [search](#search)
- [pattern](#pattern)
- [findall](#findall)
- [finditer](#finditer)
- [面向对象](#面向对象)
- [命名空间](#命名空间)
- [module](#module-1)
- [module name](#module-name)
- [module statements](#module-statements)
- [when statements will be executed](#when-statements-will-be-executed)
- [from ... import ...](#from--import--1)
- [as](#as)
- [execute module as script](#execute-module-as-script)
- [module search path](#module-search-path-1)
- [`sys.path`初始化](#syspath初始化)
- [standard module](#standard-module)
- [dir function](#dir-function)
- [packages](#packages)
- [import \* from package](#import--from-package)
- [包内部相互引用](#包内部相互引用)
- [class](#class)
- [python scope and namespaces](#python-scope-and-namespaces)
- [namespace](#namespace)
- [lifetime](#lifetime)
- [LGEB](#lgeb)
- [scope](#scope)
- [global](#global)
- [nonlocal](#nonlocal)
- [global namespace](#global-namespace)
- [Class Objects](#class-objects)
- [__init__](#init)
- [data attributes](#data-attributes)
- [method object](#method-object)
- [class and instance variables](#class-and-instance-variables)
- [function object](#function-object)
- [Inheritance](#inheritance)
- [attribute resolving](#attribute-resolving)
- [method resolving](#method-resolving)
- [built-in functions](#built-in-functions)
- [private variables](#private-variables)
- [name mangling](#name-mangling)
- [odds and ends](#odds-and-ends)
- [`__self__, __func__`](#__self__--__func__)
- [iterators](#iterators)
- [generators](#generators)
- [generator expression](#generator-expression)
- [asyncio](#asyncio)
- [Coroutines](#coroutines)
- [Awaitables](#awaitables)
- [Coroutinues](#coroutinues)
- [Tasks](#tasks)
- [Futures](#futures)
- [Creating Tasks](#creating-tasks)
- [`asyncio.create_task(coro, *, name=None, context=None)`](#asynciocreate_taskcoro--namenone-contextnone)
- [Task Cancellation](#task-cancellation)
- [Task Groups](#task-groups)
- [`asyncio.TaskGroup`](#asynciotaskgroup)
- [`taskgroup.create_task(coro, *, name=None, context=None)`](#taskgroupcreate_taskcoro--namenone-contextnone)
- [CancelledError](#cancellederror)
- [terminate a taskgroup](#terminate-a-taskgroup)
- [Sleeping](#sleeping)
- [`async asyncio.sleep(delay, result=None)`](#async-asynciosleepdelay-resultnone)
- [并发运行任务](#并发运行任务)
- [`awaitable asyncio.gather(*aws, return_exceptions=False)`](#awaitable-asynciogatheraws-return_exceptionsfalse)
- [return\_exceptions](#return_exceptions)
# Python
## 变量
在python中可以通过如下方式创建变量
```python
message = "Hello Python Crash Course world!"
```
## 字符串
### 字符串首字母大写
```python
name = "ada lovelace"
print(name.title())
```
### 字符串全部字符大写
```python
name = "Ada Lovelace"
print(name.upper())
```
### 字符串全部字符小写
```python
name = "Ada Lovelace"
print(name.lower())
```
### 字符串删除空白符
```py
name = " Asahi Ogura `
# 删除左空白
name.lstrip()
# 删除右空白
name.rstrip()
# 删除左右空白
name.strip()
```
### 访问字符串中字符
ptyhon可以通过数组下标来访问字符串中的字符如果字符串长度为n那么索引范围为`[0, n-1]`同时在python中还可以使用负数的索引索引范围为`[-n, -1]`
在python字符串中第一个字符可以通过索引下标`0``-n`来访问,最后一个字符可以通过`n-1``-1`来访问。
### 字符串切片
字符串切片可以通过`str[from:to:step]`来标识,其标识下标为`[from, to)`范围内的子字符串,步长为`step`
```py
temp = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
```
其中,输出`[0,10)`范围内的字符串可以采用如下方式:
```py
print(temp[0:10]) # ABCDEFGHIJ
print(temp[0:10:1]) # ABCDEFGHIJ
```
当步长被设置为2时输出内容如下
```py
print(temp[0:10:2]) # ACEGI
```
正数索引和负数索引混用:
```py
# 输出不包含末尾2个字符的子字符串
print(temp[0:-2]) # ABCDEFGHIJKLMNOPQRSTUVWX
print(temp[0:24]) # ABCDEFGHIJKLMNOPQRSTUVWX
print(temp[:-2]) # ABCDEFGHIJKLMNOPQRSTUVWX
# 输出末尾两个字符的子字符串
print(temp[-2:])
```
逆序输出字字符串
```py
print(temp[::-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA
print(temp[-1:0:-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA
print(temp[25:0:-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA
```
### 字符串迭代
python中字符串可迭代示例如下
```py
temp = "ABCDE"
for ind, ch in enumerate(temp):
print("ind = "+str(ind)+", ch = "+ch, end="\n")
# ind = 0, ch = A
# ind = 1, ch = B
# ind = 2, ch = C
# ind = 3, ch = D
# ind = 4, ch = E
```
## 数字
### /
python中整数执行`/`操作默认并不会进行整除,而是可能生成浮点数
```py
print(3/2) # 1.5
```
### //
python中如果要执行整除应该使用`//`运算符,该运算符为向下取整
```py
print(3//2) # 1
print(-3//2) # -2
```
> 和java不同java在执行整数除时会舍弃掉小数部分例如`-3/2`java中结果为-1而python则是向下取整为-2
>
> 如果python中`/`操作想要和java保持一直可以使用math中的`trunc`函数,`math.trunc(-3/2)`返回结果和java中一样为`-1`
### 数字类型向字符串类型转换
可以通过str函数将数字类型转化为字符串类型
```py
temp = 1.2
print("temp = "+str(temp))
```
## 列表
python中通过`[e1, e2, ...]`的形式来标识列表
### 访问列表中元素
可以通过数组下标的方式来访问列表中的元素
```py
temp = [1, "2", "san"]
print(temp[1]) # 2
print(temp[-2]) # 2
```
### 列表元素操作
#### 修改
```py
temp = [1, "2", "san"]
temp[2] = 3.0
print(temp) # [1, '2', 3.0]
```
#### 插入到末尾
如果想要向数组末尾插入元素可以调用append方法
```py
temp = [1, "2", "san"]
temp.append("four")
print(temp) # [1, '2', 'san', 'four']
```
#### 在某位置之前插入
如果想要在特定位置之前插入元素可以调用insert方法
```py
temp = [1, "2", "san"]
temp.insert(1, "1.5")
print(temp) # [1, '1.5', '2', 'san']
temp.insert(4, "four")
print(temp) # [1, '1.5', '2', 'san', 'four']
```
#### 删除列表中的元素
如果想要删除列表中的元素可以通过del关键字进行删除
```py
temp = [1, "2", "san"]
del temp[1]
print(temp) # [1, 'san']
```
#### 列表与栈api
列表append操作是将元素追加到列表末尾同时列表还支持pop操作将列表末尾的元素作为栈顶元素弹出。
如果此时列表为空那么调用pop方法将抛出异常
```py
temp = [1, "2", "san"]
print(temp.pop()) # san
print(temp.pop()) # 2
print(temp.pop()) # 1
print(temp.pop()) # IndexError: pop from empty list
```
还可以为pop方法指定一个下标此时可以弹出任何位置的元素
```py
temp = [1, "2", "san"]
print(temp.pop(1)) # 2
print(temp.pop(0)) # 1
print(temp.pop(0)) # san
```
#### remove
在列表中可以调用remove来移除列表中值为xxx的元素。
remove默认只会移除第一个匹配的元素
```py
temp = [2, 1, 2, 1, 2]
temp.remove(2)
print(temp) # [1, 2, 1, 2]
```
### 列表排序
#### sort
可以通过sort方法针对列表中的元素进行排序sort方法会该表列表中的元素顺序
```py
temp = [2, 1, 2, 1, 2]
temp.sort()
print(temp) # [1, 1, 2, 2, 2]
```
如果想要逆向排序可以为sort方法指定`reverse=True`
```py
temp = [2, 1, 2, 1, 2]
temp.sort(reverse=True)
print(temp) # [2, 2, 2, 1, 1]
```
#### sorted
如果不想改变原列表中的元素顺序而是生成一个排好序的新列表可以调用sorted函数
```py
temp = [2, 1, 2, 1, 2]
print(sorted(temp)) # [1, 1, 2, 2, 2]
print(sorted(temp, reverse=True)) # [2, 2, 2, 1, 1]
print(temp) # [2, 1, 2, 1, 2]
```
### 列表中顺序反转
如果不想对列表中元素进行排序只是想逆序列表中的数据可以调用reverse方法
```py
temp = [2, 1, 2, 1, 3]
temp.reverse()
print(temp) # [3, 1, 2, 1, 2]
```
### 获取列表长度
可以通过len函数获取列表的长度
```py
temp = [1,2,3]
temp.pop()
print(len(temp)) # 2
temp.pop()
print(len(temp)) # 1
temp.pop()
print(len(temp)) # 0
```
### 遍历列表
在python中可以通过`for ... in ...`的形式来遍历列表中的元素
```py
temp = [1, 2, 3]
for e in temp:
print(e)
# 1
# 2
# 3
```
### range
在python中可以通过range函数生成一系列数字`range(m,n)`会生成`[m,n)`范围内的数字,可用于`fori`形式的遍历
```py
arr = ["1", "3", "2"]
for ind in range(0, len(arr)):
print(arr[ind])
# 1
# 3
# 2
```
可以通过range函数来创建数字列表
```py
print(list(range(0, 5))) # [0, 1, 2, 3, 4]
```
range也可以指定步长
```py
print(list(range(0, 7, 2))) # [0, 2, 4, 6]
```
### max, min, sum
针对数字型的列表支持max、min、sum等函数来求最大值、最小值、总和
```py
arr = list(range(1, 101))
print(max(arr)) # 100
print(min(arr)) # 1
print(sum(arr)) # 5050
```
### 根据一个列表生成另一个列表
python中根据`[expr(e) for e in list_a]`的语法,可以快速根据一个列表得到应一个列表,目标列表中的每个元素都来源于来源列表的映射
```py
arr = ['amd', 'nvidia']
graphic_msgs = ['fuck '+brand for brand in arr]
print(graphic_msgs) # ['fuck amd', 'fuck nvidia']
```
### 切片
列表的切片和字符串切片类似
```py
arr = [1, 2, 3, 4, 5]
print(arr[::2]) # [1, 3, 5]
print([e**2 for e in arr[::2]]) # [1, 9, 25]
```
### 列表复制
在python中如果要复制列表可以创建整个列表的切片
```py
arr = [1, 2, 3]
# 复制列表
arr_copied = arr[:]
print(arr_copied) # [1, 2, 3]
# 对复制后的列表进行写操作,并不会影响复制前的列表
arr_copied.pop()
print(arr_copied) # [1, 2]
print(arr) # [1, 2, 3]
```
## 元组
运行时列表中的元素可以被修改,如果想要让元素不可变,可以使用元组。
元组通过`(e1, e2, e3, ...)`形式来表示,和列表一样,元组也可以通过索引来访问。
但是元组中的元素无法复制,在执行赋值语句时会抛出异常。
```py
pos = (50, 100)
pos[0] = 125 # TypeError:'tuple' object does not support item assignment
```
## if
类似其他编程语言python也支持if/else语法
```py
cars = ['audi', 'bmw', 'subaru', 'toyota']
for car in cars:
if car == 'bmw':
print(car.upper())
else:
print(car.title())
```
### and/or
python可以通过`and`和`or`操作符来拼接多个条件。
```py
age = 179
is_alive = True
if age < 0 or age > 150 and is_alive:
print("age wired")
```
### 列表中是否包含某值
如果想要检查列表中是否包含特定值,可以采用`xxx in list_a`的语法。
```py
arr = [1, 3, 2]
print(1 in arr) # True
```
### 列表中是否不包含某值
如果想要检查列表中是否不包含特定值,可以采用`xxx not in list_a`的语法。
```py
arr = [1, 3, 2]
print(1 not in arr) # False
```
### 多分支if/elif/else
python可以通过if/elif/else的语法实现多分支判断
```py
age = 40
if age < 20:
print("teen")
elif age < 40:
print("man")
else:
print("elder man")
```
## 字典
在python中通过字典实现了其他语言中的Map数据结构。
```py
dic = {"name": "Ogura Asahi", "age": 17}
print(dic['name']) # Ogura Asahi
```
### 向字典中添加键值对
```py
dic = {"name": "Ogura Asahi", "age": 17}
dic['major'] = "student"
print(dic) # {'name': 'Ogura Asahi', 'age': 17, 'major': 'student'}
```
### 删除字典中的键值对
```py
dic = {"name": "Ogura Asahi", "age": 17}
del dic['age']
print(dic) # {'name': 'Ogura Asahi'}
```
### 字典遍历
字典遍历可以通过`for k, v in dic.items()`的形式完成
```py
dic = {"name": "Ogura Asahi", "age": 17}
for key, val in dic.items():
print("key = "+key+" , value = "+str(val))
```
如果只想遍历字典的key集合可以通过`for k in dic.keys()`的方式
```py
dic = {"name": "Ogura Asahi", "age": 17}
for key in dic.keys():
print("key = "+key)
```
如果只想遍历字典的value集合可以通过`for v in dic.values()`的形式
```py
dic = {"name": "Ogura Asahi", "age": 17}
for value in dic.values():
print("value = "+str(value))
```
### 按顺序遍历字典的key
通常字典的key遍历顺序是不一定的。如果想要按排序后的顺序遍历key可以采用如下方式
```py
dic = {"name": "Ogura Asahi", "age": 17}
for key in sorted(dic.keys()):
print("key = "+str(key))
```
## while
```py
i = 0
while i < 10:
print(i)
i += 1
```
## 函数
函数可通过如下方式来定义:
```py
def fuck(brand, boss):
print("fuck you " + boss + " and your " + brand)
fuck("Nvidia", "Jensen Huang")
```
在调用函数时,可以打乱传入实参顺序,只需要传参时指定参数名称即可:
```py
def fuck(brand, boss):
print("fuck you " + boss + " and your " + brand)
fuck(boss="Jensen Huang", brand="Nvidia")
```
### 参数默认值
声明函数时,可以为参数指定默认值,故而在调用函数时,可以对有默认值的参数进行省略
```py
def fuck(boss, brand="Nvidia"):
print("fuck you " + boss + " and your " + brand)
fuck("Jensen Huang")
fuck(brand="Apple", boss="Cook")
```
### 接收多个参数
通过`*paramTup`的形式python会将函数接收到的参数都存储在一个元组中。
```py
def fuck(*name_list):
msg = "fuck "
is_head = True
for name in name_list:
if not is_head:
msg += ", "
is_head = False
msg += name
print(msg)
fuck("Nvidia", "Amd")
```
## 文件操作
### 文件读取
```py
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
```
### 写入文件
```py
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")
```
### 文件末尾追加
```py
filename = 'programming.txt'
with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")
```
## module
在python中支持将一些公用的实现放在一个文件中在使用时可以在其他script中导入该文件并对公共实现进行调用。
此时放置公共实现的文件称为modulemodule中的定义可以被其他module导入。
module是一个包含python定义和statement的文件`file name`是`module name`加上`.py`。在module中module name可以通过global变量`__main__`来进行访问。
module中可以包含executable statement和函数定义statements则是为了初始化module。executable statement只会在module被初次import时执行。
每个module都会有其独有的private namespace该namespace会用作module中所有函数的global namespace。故而module的作者可以放心的在mdoule中使用global变量module global变量有其自己的namespace并不用担心module global变量和调用方的global变量发生冲突。
故而,可以通过`modname.itemname`来访问module中的global变量。
如果在`module A`中对`module B`执行了导入操作,那么被导入的`module B`中的names将会被全部添加到`module A`的global namespace中。
导入语法如下:
```python
# 直接导入fibo中的names
from fibo import fib, fib2
fib(500)
```
```python
# 从module中导入所有names_开头的name不会被导入
from fibo import *
fib(500)
```
```python
# 如果import语句后跟随了as那么as后跟随的名称将会和被导入module绑定
import fibo as fib
fib.fib(500)
```
```python
# 和上一个的效果相同module通过`fibo`来引用
import fibo
```
```python
from fibo import fib as fibonacci
fibonacci(500)
```
### 运行module
当想要运行module时可以采用如下方法
```bash
python fibo.py <arguments>
```
当有一段代码只有当module以main module的情况下被调用时才会被执行可以采用如下方式
```python
if __name__ == '__main__':
# run some code
```
### module search path
当module被导入时python interpreter首先会在`sys.builtin_module_names`中搜寻module namebuilt-in-module-names中包含如下module
```ptyhon3
>>> import sys
>>> sys.builtin_module_names
('_abc', '_ast', '_bisect', '_blake2', '_codecs', '_collections', '_csv', '_datetime', '_elementtree', '_functools', '_heapq', '_imp', '_io', '_locale', '_md5', '_operator', '_pickle', '_posixsubprocess', '_random', '_sha1', '_sha256', '_sha3', '_sha512', '_signal', '_socket', '_sre', '_stat', '_statistics', '_string', '_struct', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'array', 'atexit', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'fcntl', 'gc', 'grp', 'itertools', 'marshal', 'math', 'posix', 'pwd', 'pyexpat', 'select', 'spwd', 'sys', 'syslog', 'time', 'unicodedata', 'xxsubtype', 'zlib')
>>>
```
如果builtin_module_names中不包含被导入的module name那么其会在`sys.path`中寻找`{module}.py`文件,`sys.path`通过如下路径来初始化:
- input script所处的目录路径
- `PYTHONPATH`全局变量,语法和`PATH`相同
- 安装包默认位置
### dir
通过内置的dir函数可以输出module中的names
```bash
>>>import fibo, sys
>>>dir(fibo)
['__name__', 'fib', 'fib2']
>>>dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
'__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
'__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
'_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
'warnoptions']
```
当dir函数传参为空时输出当前namespace的names
```bash
>>>a = [1, 2, 3, 4, 5]
>>>import fibo
>>>fib = fibo.fib
>>>dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
```
dir并不会输出内置的names内置的names定义在`builtins`module中
```bash
>>>import builtins
>>>dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
'__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
'zip']
```
### package
假设python项目结构如下package和子package中包含多个moduels
```
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
```
在导入包时会在sys.path中搜索package的子目录。
如果需要python将目录视为package需要在目录下加入`__init__.py`文件,该文件可以为空。
使用者可以导入package中独立的模块
```python
import sound.effects.echo
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
```
```python
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)
```
#### from ... import *
对于`from {package} import *`,其会查看是否包下定义的`__init__.py`文件中定义了`__all__`变量,如果定义了,那么会导入`__all__`中的所有module。
```python
__all__ = ["echo", "surround", "reverse"]
```
## Errors and Exceptions
在python中存在两种不同的异常`syntax erros`和`exceptions`
### Syntax Errors
syntax errors又被称为parsing errors当parser检测到语法错误时会抛出该error
### Exceptions
在python程序执行时即使语法都正常也有可能在执行时发生异常。在程序执行时被检测到的异常被称为`exceptions`但是其时可以被处理的并不会无条件的造成fatal。
### handling exceptions
可以在程序中对特定类型的异常进行处理,示例如下所示:
```py
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops! That was no valid number. Try again...")
```
上述示例中针对`ValueError`类型的异常进行了捕获,并提示用户输入有效的数字。但是,在上述程序实例中,用户仍然能够通过`Ctrl + C`中断该程序。
> `Ctrl + C`将会以抛出`KeyboardInterrupt`的形式表现。
try statement中可以包含多个except为不同类型的异常指定handler最多只会有一个handler被执行。并且一个except中也可以包含多个exceptions示例如下所示
```py
... except (RuntimeError, TypeError, NameError):
... pass
```
包含多个except的示例如下所示:
```py
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
```
上述示例中,会输出`B, C, D`。但是,如果修改`except B`的位置为第一位,那么输出将会变为`B, B, B`。
在except语法中除了指定异常类型外还支持指定一个变量名来捕获异常该变量中包含`args`属性,能够访问`构造该异常对象时传递的参数`,并且,异常中还定义了`__str__()`方法用于打印所有的异常参数。
为捕获的异常绑定变量的示例如下所示:
```py
try:
raise Exception('spam', 'eggs')
except Exception as inst:
print(type(inst)) # the exception type
print(inst.args) # arguments stored in .args
print(inst) # __str__ allows args to be printed directly,
# but may be overridden in exception subclasses
x, y = inst.args # unpack args
print('x =', x)
print('y =', y)
```
输出内容为:
```
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
```
#### python异常结构
`BaseException`是所有异常类的common base class而`Exception`类则是`BaseException`的子类同时也是所有非fatal异常类的base class。
对于是`BaseException`子类但是不是`Exception`子类的类,其通常代表不应当被处理的异常,抛出该类异常通常代表程序应当被终止,其包含如下类:
- `SystemExit`:由`system.exit()`抛出
- `KeyboardInterrupt`:由想要中止程序的用户触发`Ctrl + C`
`Exception`可以被用做匹配所有异常。
```py
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error:", err)
except ValueError:
print("Could not convert data to an integer.")
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
```
`try-except`语法还支持`else``else`必须跟在所有`except`之后,其代表`try block`中没有抛出任何异常的场景。
```py
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
```
### rasing exception
`raise`关键字支持抛出异常,示例如下
```py
raise NameError('HiThere')
```
传递给raise的必须是`Exception`的实例或是`继承自Exception的类`但传递的是类时其会使用类的默认构造器来构造Exception实例。
```py
raise ValueError # shorthand for 'raise ValueError()'
```
`raise`关键字也能用于对异常的重新抛出,示例如下:
```py
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
```
### Exception Chaining
如果在`except`处理异常时处理逻辑又抛出了异常那么其会将被处理的异常关联到新异常中并且将被处理异常包含在error msg中
```py
try:
open("database.sqlite")
except OSError:
raise RuntimeError("unable to handle error")
```
其输出如下:
```
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
open("database.sqlite")
~~~~^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("unable to handle error")
RuntimeError: unable to handle error
```
如果想要设置异常的cause可以使用`from`语法,示例如下所示:
```py
def func():
raise ConnectionError
try:
func()
except ConnectionError as exc:
raise RuntimeError('Failed to open database') from exc
```
上述示例中,`raise ... from exc`直接表明RuntimeError由exc引发。
输出示例如下所示:
```py
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
func()
~~~~^^
File "<stdin>", line 2, in func
ConnectionError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database
```
通过`from None`也可以关闭异常chain示例如下
```py
try:
open('database.sqlite')
except OSError:
raise RuntimeError from None
```
示例如下所示:
```py
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError from None
RuntimeError
```
### 用户自定义异常类型
用户如果要自定义异常,可以创建`Exception`类型的子类。
大多数异常类型都以`Error`结尾和standard exceptions的命名规范类似。
### define cleanup action
python支持通过`finally`来定义clean-up action其在所有场景下都会被处理。
```py
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world!')
```
其中finally执行时机如下
- 当抛出异常时如果异常没有被except处理那么异常在finally执行完后再次抛出
- 当except或else中抛出异常时那么异常在finally执行完后被抛出
- 如果在finally中执行了`break, continue, return`等语句,那么异常不会被抛出
- 如果try中执行了`break, continue, return`那么finally会在`break, continue, return`执行之前执行
- 如果finally中执行了return操作那么其return的值将会覆盖try中return的值
finally通常被用于释放资源等操作。
### predefined clean-up action
部分对象定义了对象不在被需要时的clean-up action,无论针对对象的操作是否成功clean-up action都会被执行。
故而,可以通过`with`对该类对象进行使用,并无需手动释放其资源,示例如下:
```py
with open("myfile.txt") as f:
for line in f:
print(line, end="")
```
在上述代码执行完后,文件会自动关闭,即使是在执行过程中遇到异常。
### rasing multi exceptions
在部分时候,可能需要抛出多个异常,此时可以通过`ExceptionGroup`来对exception list进行wrap示例如下
```py
def f():
excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)
```
#### except*
当使用`except*`时可以选择性处理group中特定类型的异常并将其他异常传递到下游。
```py
def f():
raise ExceptionGroup(
"group1",
[
OSError(1),
SystemError(2),
ExceptionGroup(
"group2",
[
OSError(3),
RecursionError(4)
]
)
]
)
try:
f()
except* OSError as e:
print("There were OSErrors")
except* SystemError as e:
print("There were SystemErrors")
```
当使用`except*`语法时,即使指定了`except* OSError as e``e`的类型仍然是`ExceptionGroup`而不是`OSError`因为try中抛出的`ConditionGroup`中可能含有多个`OSError`类型的异常,故而,实际的`OSError`异常对象需要通过`e.exceptions`来进行访问。
### enriching exceptions with notes
当创建异常时,可以通过`add_note(note)`方法来为异常补充信息标准的traceback会按照其被添加的顺序渲染所有的note信息note信息在异常信息之后
```py
try:
raise TypeError('bad type')
except Exception as e:
e.add_note('Add some information')
e.add_note('Add some more information')
raise
```
其输出如下:
```py
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information
```
```py
def f():
raise OSError('operation failed')
excs = []
for i in range(3):
try:
f()
except Exception as e:
e.add_note(f'Happened in Iteration {i+1}')
excs.append(e)
raise ExceptionGroup('We have some problems', excs)
```
其异常信息如下:
```
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| raise ExceptionGroup('We have some problems', excs)
| ExceptionGroup: We have some problems (3 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 1
+---------------- 2 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 2
+---------------- 3 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 3
+------------------------------------
```
## 多线程
python可以通过引入threading和concurrent.futures两个包来引入多线程
```py
import math
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
import requests
ts_arr = []
lock = threading.Lock()
def query_ts():
try:
body = requests.get("https://api.m.taobao.com/rest/api3.do", {
"api": "mtop.common.getTimestamp"
}).json()
except Exception as e:
print(e)
raise
def query_ts_once():
ts_beg = time.time_ns()
query_ts()
ts_end = time.time_ns()
ts_spend = math.trunc((ts_end - ts_beg) / 1000000)
with lock:
ts_arr.append(ts_spend)
def stress_test(test_cnt):
with ThreadPoolExecutor(max_workers=32) as executor:
i = 0
f_list = []
while i < test_cnt:
f_t = executor.submit(query_ts_once)
f_list.append(f_t)
i += 1
as_completed(f_list)
stress_test(100)
print(f"max ts : {max(ts_arr)} ms")
print(f"min ts : {min(ts_arr)} ms")
print(f"avg ts : {sum(ts_arr) / len(ts_arr)} ms")
print(ts_arr)
```
## linux命令交互
如果想要调用系统linux的命令可以使用subprocess模块。
```py
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)
```
调用示例如下:
```py
ret = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(ret.stdout)
```
输出如下:
```bash
total 4
-rwxrwxrwx 1 asahi asahi 110 May 4 03:18 main.py
-rwxrwxrwx 1 asahi asahi 73 May 4 01:10 shiro.json
drwxrwxrwx 1 asahi asahi 4096 Feb 7 21:39 venv
```
## 正则
python通过re模块提供了正则表达式的支持
### match
match方法定义如下所示
```py
re.match(pattern, string, flags=0)
```
match方法使用示例如下所示
```py
import re
def main():
match_res = re.match("(?P<prefix>.*) father", "i am your father")
if match_res is None:
print("failed match")
return
print(match_res.group("prefix")) # i am your
print(match_res.group(0)) # i am your father
main()
```
### search
search方法定义如下所示
```py
re.search(pattern, string, flags=0)
```
search方法使用示例如下所示
### pattern
类似javare提供了compile方法返回正则编译后的pattern并通过pattern执行操作
#### findall
`pattern.findall`方法会返回所有匹配的字符串
```py
import re
def main():
msg = '''although i use amd graphic card, i view it as a trash for its bad performance in ai.
nvidia is also trash because its crazy price, fuck amd and nvidia!'''
pattern = re.compile("(amd|nvidia)\\s+(graphic card)?")
search_res_list = pattern.findall(msg)
print(search_res_list)
main()
# [('amd', 'graphic card'), ('nvidia', ''), ('amd', '')]
```
#### finditer
`pattern.finditer`会返回所有匹配的match对象
```py
import re
def main():
msg = '''although i use amd graphic card, i view it as a trash for its bad performance in ai.
nvidia is also trash because its crazy price, fuck amd and nvidia!'''
pattern = re.compile("(amd|nvidia)\\s+(graphic card)?")
search_res_list = pattern.finditer(msg)
print([e.group(0) for e in search_res_list])
main()
# ['amd graphic card', 'nvidia ', 'amd ']
```
## 面向对象
### 命名空间
命名空间是name到对象的映射集合在不同的命名空间中相同name映射到的对象可能会不同。
namespace的示例有
- 内置name集合包含内置异常名称和函数名例如`abs()`函数)
- module中的global names
- 函数调用中的local names
> 在某种程度上来说对象中的attributes也构成了一个命名空间
## module
python支持将文件内容拆分到多个文件中将function definition放置到文件中后在`其他文件`或`interactive instance`中可以针对fucntion definition进行导入。
放置function definition的文件被称之为`module`,通常用来定义公共方法。
### module name
module为包含python definition和statements的文件。file name由module name和`.py`后缀组成。在module中module的名称可以通过全局变量名`__name__`进行访问。
如果在文件`fibo.py`中定义如下内容:
```py
# Fibonacci numbers module
def fib(n):
"""Write Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n):
"""Return Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
```
那么`fibo`module中的内容可以通过如下方式进行导入
```py
import fibo
```
上述导入并不会将定义在`fibo`module中的function name直接添加到当前的命名空间其只是将module name`fibo`添加到了命名空间可以通过module name访问module中定义的内容
```py
fibo.fib(1000)
fibo.fib2(100)
fibo.__name__
```
### module statements
在module中除了可以包含function definition外还可以包含可执行statements。该可执行statements用于对module进行初始化。
#### when statements will be executed
module中的statements只会在`第一次该module被导入`时执行。(如果module文件其本身被执行而非作为依赖被其他文件导入那么module中的statements也同样会被执行)
每个module都拥有其自己的private namespace并且对于定义在module中的所有方法而言`private module`既是`global namespace`。故而作为module的开发者而言其可以自由的使用global variables而不用担心其global variables名称和module使用者定义的global variables发生冲突。
如果想要访问在module中定义的global variables可以使用`modname.itemname`的方式。
通常而言对于module的import被放置在文件的最外层最开始的部分`但这并非是必须的`。在文件最外层被导入的module其module name会被添加到当前文件的module glboal namespace中。
#### from ... import ...
import还存在一个变体可以从module中直接导入name而不是导入module后通过module name访问module中内容
```py
from fibo import fib, fib2
fib(500)
```
在上述示例中,`fibo`并没有被引入到当前文件的global namespace中。
除此之外还可以导入module中所有的name
```py
from fibo import *
fib(500)
```
同样的,`fibo`也不会被导入到当前global namespace中。
上述`from fibo import *`语句会导入`fibo`module中定义的所有name但是以下划线`_`开头的名称除外。
通常情况下,不要使用`from fibo import *`来进行导入因为其引入的name是不确定的可能会覆盖你所定义的部分内容。
#### as
在对其他module执行import操作时可以通过as来进行重命名
```py
import fibo as fib
fib.fib(500)
```
上述示例中,`fib`的名称将会绑定到module `fibo`。
```py
from fibo import fib as fibonacci
fibonacci(500)
```
### execute module as script
当想要将module作为script执行时可以使用如下命令
```bash
python fibo.py <arguments>
```
上述`将module作为script执行的命令`,和`导入module时对module进行执行`的行为实际上是一致的,唯一区别如下:
- `module作为script执行`: `__name__`值为`__main__`
- `module被导入`: `__name__`值为`module name`
故而在module中包含如下代码
```py
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
```
可以使module既可作为module被导入也可以作为可执行的脚本其中该代码块只会在module作为main file时才会被执行。
并且在module被导入时`sys`并不会被导入。
### module search path
当module名为`spam`的module被导入时interpreter会按照如下顺序来进行查找
1. 首先interpreter会从built-in module中查找`spam`
2. 如果步骤1没有查询到其会在`sys.path`变量定义的路径中查找名为`spam.py`的文件
#### `sys.path`初始化
在python启动时其会初始化module search path初始化后的module search path可以通过`sys.path`来进行访问。
在`sys.path`中,组成如下:
- `sys.path`中第一条entry是包含input script的目录如果存在input script
- 若未指定input script如`运行交互式shell`、`运行-c command`、`运行-m module`时,`sys.path`的第一条entry则是当前目录
- `PYTHONPATH`环境变量中定义的路径也会被添加到search path中
- 包含`standard python modules及其依赖的拓展modules`的路径也会别添加到module search path中
- 拓展modules在windows平台中后缀名为`.pyd`,在其他平台中后缀名为`.so`
- 其中,`standard python modules`是平台无关的,被称为`prefix`
- `extension modules`则是平台相关的,被称为`exec_prefix`
> python支持通过`-c`来直接运行python命令示例如下
> ```shell
> python -c 'print(1+22*33)'
> ```
>
> python还支持通过`-m`来直接运行module示例如下
> ```bash
> echo '{"name":"John","age":30}' | python -m json.tool
> ```
> 部分系统支持symlinks故而包含input script的目录路径会在input script的symlink被follow之后才进行计算。故而`包含symlink`的目录并不会被添加到sys.path中
在`sys.path`被初始化之后python程序可以对`sys.path`进行修改。
在`sys.path`中,包含被运行文件的目录路径被放在了`sys.path`中最开始的位置,代表如果运行文件路径中如果存在和`standard library module`相同名称的文件,那么运行文件路径下的同名文件将会覆盖`standard library module`。
### standard module
python附带了一个包含standard modules的library。`standard modules`中部分modules是内置在interpreter中的built modules中的内容可能并不是语言核心操作的一部分但是能够提供对操作系统的访问例如系统调用等。
built-in modules中的module在不同平台是可选的例如`winreg` module只存在于windows平台。但是`sys`module在所有平台的python interpreter中都包含。
### dir function
内置的`dir()`方法会查看在module中定义了哪些名称其返回了一个sorted string list
```bash
>>>import fibo, sys
>>>dir(fibo)
['__name__', 'fib', 'fib2']
>>>dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
'__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
'__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
'_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
'warnoptions']
```
当没有为`dir()`方法传递参数时,`dir()`方法会列出当前已经定义的名称:
```python
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
```
其列出的名称中包含所有类型变量、module、function等
`dir`方法并不会列出内置的方法和变量,如果想要查看那些,可以使用`dir(builtins)`
```bash
import builtins
dir(builtins)
```
### packages
packge是一种结构化python modules命名空间的方法其使用了`dotted module names`。例如module name为`A.B`其代表的是位于package A下的submodule B。
就像module可以使module的开发者们不用担心使用了和其他module中一样的变量/函数名一样dotted module name可以令module的开发者不用担心使用了和其他module相同的module name。
如果你要定义一系列的modules统一处理不同格式的sound files和sound data可以采用如下的package结构
```
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
```
当对package进行导入时python会从`sys.path`中查询package的子目录。
其中,`__init__.py`文件用于`让python将该目录看作是package`。`__init__.py`文件可以是空文件,但是`也可以包含对package进行初始化的代码`或`设置__all__变量`。
在使用package中可以单独导入package中的module
```py
import sound.effects.echo
```
上述示例中导入了`sound.effects.echo`module在对module进行使用时必须采用如下方式指定mdoule的fullname
```py
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
```
另一种可选的导入方式如下:
```py
from sound.effects import echo
```
在使用module时只需要指定echo即可
```py
echo.echofilter(input, output, delay=0.7, atten=4)
```
同时也可以直接导入module中的方法
```py
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
```
在使用`import item.subitem.subsubitem`这类语法时除了最后的item外其余item必须都要是package最后的item可以是package或module但不能是module中的内容。
#### import * from package
在对package执行`import *`的语法时,其导入行为准许如下规范:
- 如果package的`__init__.py`文件中定义了`__all__`变量,其将认为`__all__`中定义了所有应该被`import *`所导入的module name
故而package的开发者应当负责在package版本迭代时将`__all__`进行更新;当然,开发者页可以决定不对`import *`进行支持,不定义`__all__`。
示例如下,`sound/effects/__init__.py`中可能包含如下内容:
```py
__all__ = ["echo", "surround", "reverse"]
```
其代表`from sound.effect import *`将会导入`echo, surround, reverse`三个module。
注意本地定义的名称可能会遮挡submodules。例如如果在`sound/effects/__init__.py`中定义了一个reverse函数那么`from sound.effects import *`将只会导入`echo, surround`两个module。
```py
__all__ = [
"echo", # refers to the 'echo.py' file
"surround", # refers to the 'surround.py' file
"reverse", # !!! refers to the 'reverse' function now !!!
]
def reverse(msg: str): # <-- this name shadows the 'reverse.py' submodule
return msg[::-1] # in the case of a 'from sound.effects import *'
```
如果在`__init__.py`中没有定义`__all__`,那么`from sound.effects import *`只会导入`__init__.py`中的其他定义内容,示例如下:
```py
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
```
### 包内部相互引用
在同一个包中不同subpackage中的module可以对彼此进行引用。引用支持绝对路径和相对路径例如`sound.filters.vocoder`在对`sound.effects`中定义的包进行import时可以采用如下方式
- 绝对路径:`from sound.effects import echo`
- 相对路径:`from ..effects import echo`
在使用相对路径的导入时,示例如下:
当从`surround`执行相对路径导入时,可以执行如下形式的导入
```py
from . import echo # 位于相同的subpackage中
from .. import formats # 访问../formats
from ..filters import equalizer # 访问../filters中的equalizer
```
## class
python在对对象进行传递时只支持引用传递传递指向对象的指针而不支持类似c++的值传递对象拷贝。故而在python中如果对方法的入参对象进行了修改那么该修改对调用方可见。
### python scope and namespaces
#### namespace
namespace代表name到object的映射。
常见的namespace有
- 包含built-in names的namespacebuilt-in names包含类似`abs`之类的built-in函数以及built-in exception names等
- 包含module global names的module namespace
- 在函数调用时包含`local names`的local namespace
从某种意义上说对象中的attributes也构成了一个namespace。
对于`module.name`的访问,其实际也是针对`module object`中`attribute`的访问module中的global name和module attribute都是位于同一namespace中。
`attribute`分为`read-only`和`writable`的。对于`writable`的attribute可以对其进行赋值示例如下
```py
modname.the_answer = 42
```
同样的,`writable`的attribute也可以通过`del`来进行删除删除后该attribute将会从对象中被移除
```py
del modname.the_answer
```
##### lifetime
在不同时机创建的namespace拥有不同的生命周期
- `built-in namespace`: 在python interpreter启动时被创建并且该namespace永远不会被删除
- `module namespace`: 当module的定义被读入时会创建module namespace并且module namespace会持续到interpreter退出时
- 对于被interpreter执行的top-level statements通过交互式输入或从script中读取其被视作`__main__`module的一部分其拥有属于自己的namespace
- `local namespace of function`:在函数被调用时创建,在函数执行完成/抛出未处理异常时namespace被删除对于递归调用每次调用都拥有其自己的namespace
##### LGEB
在python中LEGB规则决定了命名空间搜索的顺序
- `local`: 定义在函数/类内部
- `enclosed`:定义在闭包函数内
- `global`:定义在全局/最上层
- `built-in`python中内置的保留名称
<img src="https://picx.zhimg.com/v2-9e322a731ae838a75dd2de84dae659e7_1440w.jpg" data-size="small" data-rawwidth="300" data-rawheight="260" data-original-token="v2-00f7cd30194a00ffd46c4fd5612b82c2" class="content_image" width="300">
##### scope
scope代表python程序中的一个文本范围在该范围内可以对某个namespace中的name进行直接访问而无需前缀`x.y.`之类的限定名。
在程序执行的某个时间点都会存在3/4个nested scopes可以直接访问
- 最内层的scope包含local names最先查找
- scope of enclosing function查找时会从nearest neclosing scope开始包含`non-local and non-global names`
- `next-to-last scope`包含global names
- 最外层scope最后查找包含built-in names
##### global
如果使用`global var_name`的方式来声明变量那么针对该变量的引用和复制都会直接指向global namespace中的变量。
##### nonlocal
如果想要在local namespace中访问enclosing namespace中的name可以通过`nonlocal var_name`形式来声明变量,示例如下:
```py
def main():
s = "fuck"
def s_change(p):
nonlocal s
s=p
s_change("shit")
print(s)
```
如果没有通过`nonlocal`进行声明那么对于s的赋值将会看作是`在innermost scope中创建了一个同名变量并进行赋值`,外部的变量值并不会改变。
通常来说在函数定义内scope为innermost scope而在函数外则是global namespace`类定义会新开一个namespace`。
##### global namespace
对于module中定义的function其global namespace即是module namespace无论函数在哪里被调用。
在python中如果没有使用`global`或`nonlocal`那么变量是只读的对name的赋值永远会在innermost scope中。`del`语句则是从local scope对应的namespace中移除变量的绑定。
> 当在innermost scope中如果只对外层变量进行读操作那么无需使用`global`或是`nonlocal`;但是,如果想要修改外层变量,则需要使用`global`或者`nonlocal`,否则外层变量是只读的。
```py
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
```
在上述示例中,返回结果如下:
```
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
```
### Class Objects
class对象支持两种类型的操作`attribute reference`和`instantiation`。
attribute reference使用`obj.name`的语法。class定义如下:
```py
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
```
`MyClass.i`和`MyClass.f`是有效的attribute reference会返回一个integer和一个function。
`__doc__`也是一个有效的attribute返回对该class的简单文档描述。
类实例化类似于函数的调用,其语法如下:
```py
x = MyClass()
```
上述示例会创建一个MyClass类型的对象并且将其赋值给本地变量x。
#### __init__
python中支持在初始化时为对象指定状态故而可以自定义构造器示例如下
```py
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
c = Complex(3.0, -4.5)
```
#### data attributes
python中的data attributes类似于c++中的data members但是`data attributes`并不需要被声明其类似于local variables其在第一次赋值时就存在了。
例如,`变量x是MyClass类的实例`,那么如下代码将会输出`16`.
#### method object
除了通过data attribute外另一种attribute reference的方式是method。
在python中class中的function定义了object中的method。但是`x.f`和`MyClass.f`并不等价前者是method object后者是function object。
通常method在其所绑定的对象后调用
```py
x.f()
```
python中`x.f`为一个method object支持被存储在变量中并可以在后续通过变量调用示例如下
```py
xf = x.f
while True:
print(xf())
```
如上所示method object `x.f`被存储在local variable `xf`中后,后续可以通过`xf()`来调用method object。
> 按照`MyClass`的类定义,`x.f`方法将会接收一个`self`参数,但是在调用`xf()`时,并没有传递任何参数。
实际上对于method而言其method所绑定的对象实例将会作为参数被传向function的第一个参数`self`。
故而对于method object和function object而言`x.f()`实际上等价于`MyClass.f(x)`。
对于method object而言`obj.method(arg1, arg2 ...)`的调用等价于`Class.method(obj, arg1, arg2 ...)`的function调用。
### class and instance variables
简单来说每个对象的instance variables是相互独立的但是class variables则是在同一类的所有对象间都是共享的。
```py
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
```
对于class variables而言其既可以通过`MyClass.name`来进行访问,也可以通过`obj.name`来进行访问。
但是在class variable和instance variable同名时instance variable会优先被访问示例如下
```py
class Warehouse:
purpose = 'storage'
region = 'west'
w1 = Warehouse()
print(w1.purpose, w1.region)
storage west
w2 = Warehouse()
w2.region = 'east'
print(w2.purpose, w2.region)
storage east
```
在python中用户既可以通过method来访问data attributes也可以通过对象直接访问。并且python中也没有类似其他语言中`private`这类强制隐藏的机制,`python中都是基于约定`。
在使用data attributes时应当小心如下场景
- data attributes可能被method维护和管理若用户不经过method直接修改attribute的值可能会造成attribute的状态被破坏没有经过method中的相关校验直接将attribute改为错误值
- 用户可以在`避免命名冲突`的前提下向data attributes添加自己的值
在python中对象中method的第一个参数名为`self`,这只是一个约定,`self`在python中并没有特别的意义。
#### function object
python中function object为一个class attribute用于定义method。function object并不一定要定义在class内部示例如下
```py
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
```
在上述示例中,类`C`包含`f, g, h`三个attributesC实例中也包含`f, g, h`三个method。其中`h`和`g`完全等价。
在python的class定义中method可以通过`self.xxx`调用其他method示例如下
```py
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
```
method中同样可以访问global namesmethod关联的global scope是method定义所位于的module。
> 在python中每个值都是一个对象其class信息存储在`object.__class__`中。
### Inheritance
在python中支持类的继承示例如下
```py
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
```
#### attribute resolving
若派生类B继承了基类A那么在对B的对象进行attribute解析时其首先会查找派生类如果在派生类中没有找到则会继续在基类中查找。
#### method resolving
method解析也会从派生类开始沿着基类逐渐查询直到找到对应的function object。
在python中派生类的方法会覆盖基类的方法用c++中的概念来理解即python中所有的method都是virtual的。
在python中如果派生类方法中想要调用基类的方法可以通过`BaseClassName.methodname(self, arguments)`,其类似于其他语言中的`super`。
#### built-in functions
python中包含如下built-in function来判断继承关系
- `isinstance`: 该方法通常用于检查是否实例属于某一个类
- `isinstance(obj, int)`
- `issubclass`: 该方法通常用于检查是否一个类派生自另一个类
- `issubclass(bool, int)`: 该调用返回为Truebool是int的子类
### private variables
python中并不存在只能从类内部访问的`private instance variable`。但是python编码中存在一个规范约束
- 以`_`开头的名称应当被作为api中的非公开部分下划线开头的可以是function, method, data member
#### name mangling
在类定义中,任何以`__spam`定义的name至少两个前缀下划线最多一个后缀下划线都会被替换为`_classname__spam`的形式。
name magling在不破坏列内部调用的场景下十分有用示例如下
```py
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
```
在上述代码中,即使在`MappingSubclass`中引入`__update`,其名称也为`_MappingSubclass__update`,和`_Mapping__update`不同,仍然不会对父类造成影响。
在类中以`__spam`定义的变量,在类外部可以以`_classname__spam`进行访问。
### odds and ends
有时想使用类似C语言中的struct惯用方法是使用`dataclasses`,示例如下所示:
```py
from dataclasses import dataclass
@dataclass
class Employee:
name: str
dept: str
salary: int
```
```py
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000
```
### `__self__, __func__`
instance method object拥有如下属性
- `__self__`: `m.__self__`代表method m关联的实例对象
- `__func__`: `m.__func__`代表method关联的function object
### iterators
在python中大多数容器对象都可以通过for-statements进行迭代
```py
for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line, end='')
```
在底层for-statement针对容器对象调用了`iter()`方法,该方法会返回一个`iterator`对象iterator对象中定义了`__next__()`方法,该方法在终止时会抛出`StopIteration`异常。
可以通过内置的`next()`方法来调用`__next__()`,示例如下所示:
```py
>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
```
如果想要为自定义类添加iterator可以在类中定义一个`__iter__()`方法,该方法返回一个带`__next__()`方法的对象。`如果一个class中包含了__next__()方法的定义那么__iter__()方法仅需返回self即可`。
```py
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
```
### generators
`Generators`可以简单的创建iterator其编写和函数类似但是当想要返回值时使用`yield statement`。
每次调用`next()`时generator都会从上次中止的地方进行恢复。示例如下所示
```py
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
```
```py
>>> for char in reverse('golf'):
print(char)
f
l
o
g
```
通过generator完成的任何事情都可以通过iterator来完成但是generator的代码更加紧凑其`__iter__`和`__next__`方法都是自动生成的。
在generator的多次next调用之间local variables的状态和执行状态也是自动保存的。
generator令iterator的编写更加简单。
### generator expression
一些简单的迭代器可以简化为表达式:
```py
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> unique_words = set(word for line in page >>> for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
```
## asyncio
python支持通过`async/await`语法编写并发代码。asyncio是多个python异步框架的基础。
### Coroutines
可以通过`async/await`的语法来编写asyncio application。例如如下代码将打印`hello`并等待1s然后打印`world`
```py
>>> import asyncio
>>> async def main():
print('hello')
await asyncio.sleep(1)
print('world')
>>> asyncio.run(main())
hello
world
```
如果仅简单调用`main()`并不会对该coroutinue进行调度
```py
>>> main()
<coroutine object main at 0x1053bb7c8>
```
为了实际的运行coroutinueasyncio提供了如下机制
- `asyncio.run`:在上述示例中,`asyncio.run`用于执行main函数的entry point
- `awaiting on coroutinue`如下代码片段将会在1s后打印`hello`并在2s后打印`world`
```py
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
```
上述示例总共耗费3s首先等待`hello` 1s再等待`world` 2s
- `asyncio.create_task()`该方法支持创建Tasks让多个coroutinue并发运行
```py
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
```
上述示例总共耗费2s等待`hello` 1s`hello`打印后再过1s打印`world`
- `asyncio.TaskGroup`:该方法相较`asyncio.create_task`提供了一个更加现代的api示例如下
```py
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(
say_after(1, 'hello'))
task2 = tg.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# The await is implicit when the context manager exits.
print(f"finished at {time.strftime('%X')}")
```
当前示例总体耗时也为2s
### Awaitables
当对象可以在await表达式中使用时该对象被称为Awaitable object。许多asyncio api被定义为接收awaitable object。
总共有3中awaitables`coroutinue, Tasks, Futures`
对于asyncio中的三种`awaitable objects`task和coroutinue/future的区别是
- task当创建task时将会将task交给event loop待后续调度`当前coro本身并不会挂起而是会继续执行`
- coro/future当`await coro`调用时则是会挂起当前coro直接执行目标coro待目标coro执行完成后才会继续执行挂起的coro
#### Coroutinues
python中coroutinue是awaitable并且可以在其他coroutinue中通过`await`调用:
```py
import asyncio
async def nested():
return 42
async def main():
# Nothing happens if we just call "nested()".
# A coroutine object is created but not awaited,
# so it *won't run at all*.
nested() # will raise a "RuntimeWarning".
# Let's do it differently now and await it:
print(await nested()) # will print "42".
asyncio.run(main())
```
#### Tasks
`Tasks`用于并发的对coroutinues进行调度。
当通过`asyncio.create_task()`将coroutinue封装在Task中时coroutinue将会自动的被调度
```py
import asyncio
async def nested():
return 42
async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())
# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
await task
asyncio.run(main())
```
#### Futures
`Future`是一个底层的awaitable对象代表一个异步操作的最终结果。
当一个Future对象被awaited时代表coroutinue将会等待直至Future执行完成。
### Creating Tasks
#### `asyncio.create_task(coro, *, name=None, context=None)`
将coroutinue封装在Task对象中并且对其进行调度。该方法会返回一个Task object。
该task将会在`get_running_loop()`返回的loop中执行如果当前线程中没有running loop那么将会抛出`RuntimeError`。
### Task Cancellation
task可以被简单和安全的取消。当task被取消时并不会立马中断task抛出`asyncio.CancelledError`而是等到task下一次执行await时才会抛出异常。
推荐通过`try/finally`来执行clean-up逻辑当显式捕获的`asyncio.CancelledError`时通常在clean-up操作完成后都应该对异常重新抛出。
asyncio中存在部分组件允许结构化的并发例如`asyncio.TaskGroup`其内部实现中使用了cancellation`如果coroutinue吞了异常将会导致asyncio中TaskGroup等组件行为异常`。
类似的,用户代码中通常也不应该调用`uncancel`。
但是,如果真的需要对`asyncio.CancelledError`进行suppress其在吞异常之后还需要调用`uncancel`来取消cancellation state。
### Task Groups
task groups整合了`task creation API`和`wait for all tasks in the group to finish`两方面,使异步代码的编写变得更加便捷。
#### `asyncio.TaskGroup`
该类是一个asynchronous context manager其中持有了一组taskstask可以通过`create_task`被添加到group中。当context manager退出时会等待所有的tasks执行完成。
##### `taskgroup.create_task(coro, *, name=None, context=None)`
该方法调用会在group中创建一个task该方法的签名和`asyncio.create_task`方法的一致。
> 如果该task group是非活跃的那么将会对给定的coro进行关闭。
task group的使用示例如下所示
```py
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(some_coro(...))
task2 = tg.create_task(another_coro(...))
print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")
```
`async with`语句会等待所有位于task group中的tasks都执行完成但是`在等待的过程中仍然可以向task group中添加新的task`。
> 可以将task group传递给coroutinue并且在corotinue中调用`tg.create_task`向task group中添加task
一旦task group中所有的task执行完成并且`async with`block退出那么将无法向task group中添加新的task。
##### CancelledError
一旦task group中任一task抛出`非CancelledError的异常`位于task group中的其他task都会被cancelled后续任何task都无法被添加到task group中。
并且,`除了添加到task group的tasks之外包含`async with`代码块的`main task`也会被标记为cancelled。`
task group抛出非`CancelledError`会导致在下一个await表达式时抛出`CancelledError`异常,但是,`CancelledError`并不会抛出`async with block`故而在async with block外无需关注async with是否会抛出`CancelledError`。
当所有task都完成后任何抛出`非CancelledError类似异常`的异常都会被整合到`ExceptionGroup`/`BaseExceptionGroup`中,并被后续抛出。
有两种异常将会被特殊对待:`KeyboardInterrupt`和`SystemExit`对于这两种异常task group仍然会取消其他task并且等待其余tasks但是原始的`KeyboardInterrupt`和`SystemExit`异常仍然会被抛出。抛出的是异常的原值而不是ExceptionGroup
对于async with body中抛出的异常其处理和task抛出异常一致。
#### terminate a taskgroup
在python标准库中并不原生支持对task group的终止。但是可以通过向task group中`添加一个抛出未处理异常的task`来实现,示例如下:
```py
import asyncio
from asyncio import TaskGroup
class TerminateTaskGroup(Exception):
"""Exception raised to terminate a task group."""
async def force_terminate_task_group():
"""Used to force termination of a task group."""
raise TerminateTaskGroup()
async def job(task_id, sleep_time):
print(f'Task {task_id}: start')
await asyncio.sleep(sleep_time)
print(f'Task {task_id}: done')
async def main():
try:
async with TaskGroup() as group:
# spawn some tasks
group.create_task(job(1, 0.5))
group.create_task(job(2, 1.5))
# sleep for 1 second
await asyncio.sleep(1)
# add an exception-raising task to force the group to terminate
group.create_task(force_terminate_task_group())
except* TerminateTaskGroup:
pass
asyncio.run(main())
```
其输出如下:
```py
Task 1: start
Task 2: start
Task 1: done
```
### Sleeping
#### `async asyncio.sleep(delay, result=None)`
该方法会阻塞`delay`秒。
如果`result`有值那么当coroutinue完成时result会被返回给调用方。
sleep会一直挂起当前任务允许其他任务执行。如果为sleep传参为0其将会让其他tasks执行可作为一种优化手段。其通常是在运行时间较长的任务中使用避免长任务一直占用event loop。
```py
await sleep(0)
```
### 并发运行任务
#### `awaitable asyncio.gather(*aws, return_exceptions=False)`
并发的运行aws中的awaitable objects。
如果aws中的某个awaitable是coroutinue那么其将自动作为task被调度。
如果所有的awaitable objects都成功执行那么结果将会聚合到returned list中。returned list中结果的顺序和aws中awaitable object的顺序一致。
##### return_exceptions
return_exceptions的值决定了gather的行为
- `False`默认如果该参数为False那么那么aws中抛出的第一个异常将会被立刻传递到`等待gather返回结果的task`aws中其他的tasks并不会被cancelled仍然会继续运行
- `True`exceptions也将被当作成功的返回结果并且被组装到returned list中
如果`gather()`被cancelled那么gather中所有未完成的awaitable objects也都会被取消。
如果aws中的任一Task或Future被取消其将被看作抛出CancelledError在这种情况下`gather()`并不会被cancelled。这样能够避免某一个task/future的取消造成gather中其他的task/future也被取消。
`asyncio.gather`的使用示例如下所示:
```py
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({number}), currently i={i}...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
return f
async def main():
# Schedule three calls *concurrently*:
L = await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
print(L)
asyncio.run(main())
# Expected output:
#
# Task A: Compute factorial(2), currently i=2...
# Task B: Compute factorial(3), currently i=2...
# Task C: Compute factorial(4), currently i=2...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3), currently i=3...
# Task C: Compute factorial(4), currently i=3...
# Task B: factorial(3) = 6
# Task C: Compute factorial(4), currently i=4...
# Task C: factorial(4) = 24
# [2, 6, 24]
```