- [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中导入该文件并对公共实现进行调用。 此时,放置公共实现的文件称为module,module中的定义可以被其他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 ``` 当有一段代码,只有当module以main module的情况下被调用时才会被执行,可以采用如下方式 ```python if __name__ == '__main__': # run some code ``` ### module search path 当module被导入时,python interpreter首先会在`sys.builtin_module_names`中搜寻module name,built-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) ``` 输出内容为: ``` ('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 "", line 2, in 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 "", line 4, in 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 "", line 2, in func() ~~~~^^ File "", line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 4, in 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 "", line 4, in 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 "", line 2, in 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 "", line 1, in | raise ExceptionGroup('We have some problems', excs) | ExceptionGroup: We have some problems (3 sub-exceptions) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "", line 3, in | f() | ~^^ | File "", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 1 +---------------- 2 ---------------- | Traceback (most recent call last): | File "", line 3, in | f() | ~^^ | File "", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 2 +---------------- 3 ---------------- | Traceback (most recent call last): | File "", line 3, in | f() | ~^^ | File "", 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.*) 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 类似java,re提供了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 ``` 上述`将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的namespace(built-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中内置的保留名称 ##### 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`三个attributes,C实例中也包含`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 names,method关联的global scope是method定义所位于的module。 > 在python中,每个值都是一个对象,其class信息存储在`object.__class__`中。 ### Inheritance 在python中,支持类的继承,示例如下: ```py class DerivedClassName(BaseClassName): . . . ``` #### 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)`: 该调用返回为True,bool是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 >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "", line 1, in 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() ``` 为了实际的运行coroutinue,asyncio提供了如下机制: - `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,其中持有了一组tasks,task可以通过`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] ```