码农高天重构S3脚本 用海象运算与语法约束防范生产级隐患

【Code Review】十行循环变两行?argparse注意事项?不易察觉的异常处理? — 码农高天

码农高天

脚本从个人工具迈向生产级不仅要用海象运算符和生成器精简分页循环,更须通过显式互斥断言、线程异常保护与资源兜底防范静默失败。

AI-assisted summary of 【Code Review】十行循环变两行?argparse注意事项?不易察觉的异常处理? — original source. Check the original for full context.

Source published:

Key ideas

  • 码农高天针对开源项目 s3_reader 进行生产级 Code Review,覆盖 argparse 规范、S3 分页循环压缩与并发资源管理等核心问题。 (0:00)
  • 利用生成器表达式与海象运算符重构 S3 分页请求逻辑,可在不牺牲可读性的前提下将冗长的分页循环压缩至两行核心代码。 (5:00)
  • 使用 ThreadPoolExecutor 进行并发 I/O 操作时,必须引入 try-finally 确保客户端资源释放,并对 job.result() 进行 try-except 兜底以防网络异常丢失其余结果。 (10:00)

Detailed notes

## Background 本视频由 YouTube 频道“码农高天”于 2024 年 4 月 27 日发布。视频内容源自作者此前商业合作承诺的抽奖活动,通过对知识星球成员开源的 AWS S3 查看与下载小工具(randomseed42/s3_reader)进行实战代码审查(Code Review),分享提升代码质量的实用技巧。

## TL;DR 高天围绕一个轻量级 S3 工具展开审查,首先指出了命令行参数解析中的规范问题,建议在 argparse 中显式声明必选参数、使用 choices 限制输入并用语法互斥替代逻辑互斥以提早捕获 Bug。随后,他针对 S3 对象分页查询逻辑进行重构,借助生成器表达式、字典 get 默认值及海象运算符,将原本十来行的冗长循环精简至数行。最后,高天重点排查了并发下载场景下的工程健壮性隐患,强调必须使用 try-finally 确保客户端资源可靠释放,对并发任务的 job.result 进行异常保护,并提炼模板代码以消除低级的复制粘贴。

## Atmosphere & style 视频整体呈现出一种沉稳专业而又亲切平易的导师式 Code Review 氛围。高天循序渐进地引导观众从“能跑通的日常脚本”思维跨越到“高健壮性的生产级代码”视角,语调温和但对边界条件与语法细节极其敏锐。观看时既能感受到代码重构后逻辑化繁为简的干脆利落,也能直观体会到资深工程师对资源泄露和网络异常等隐形陷阱的严谨推演。

## Bulletin - [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) 码农高天针对开源项目 s3_reader 进行生产级 Code Review,覆盖 argparse 规范、S3 分页循环压缩与并发资源管理等核心问题。 - [05:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=300s) 利用生成器表达式与海象运算符重构 S3 分页请求逻辑,可在不牺牲可读性的前提下将冗长的分页循环压缩至两行核心代码。 - [10:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=600s) 使用 ThreadPoolExecutor 进行并发 I/O 操作时,必须引入 try-finally 确保客户端资源释放,并对 job.result() 进行 try-except 兜底以防网络异常丢失其余结果。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) 在 pyproject.toml 配置 CLI 入口的同时,在包内提供 __main__.py 支持 python -m 模块化调用是更规范的项目组织方式。 - [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) argparse 中使用 nargs='+' 仅约束参数值个数不能为零,必须显式声明 required=True 才能防止该参数被整体省略。 - [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) 对于取值有限的命令行参数(如 output-format),应通过 choices 属性约束输入,尽早暴露传参错误。 - [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) 子命令分发逻辑应使用 elif 与 else: assert False 从语法层面强制互斥,比松散的连续 if 分支更健壮且易于排查拼写错误。 - [05:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=300s) 向列表追加推导式内容(keys += [...])会产生千量级的临时列表,改用生成器表达式与 keys.extend() 能避免不必要的中间内存开销。 - [05:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=300s) 利用 dict.get() 提供默认值与海象运算符(:=)捕获分页 token,可以将多分支且依赖 break 的分页 while 循环精简为极简结构。 - [10:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=600s) 网络 I/O 客户端的生命周期管理应使用 try-finally 结构包裹,避免中间任务崩溃导致连接未正常调用 close() 关闭。 - [10:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=600s) ThreadPoolExecutor 任务在网络波动下可能抛出异常,必须对 job.result() 增加 try-except 保护,防止单个任务失败拖垮并丢失整个并发批次的结果。 - [10:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=600s) 多个仅在调用目标与拼装格式上有微调的并发执行入口属于模板复制,应当抽取为通用的辅助函数以降低维护成本。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=0s) 项目背景与 CLI 参数解析的最佳实践

码农高天在视频开篇提到,之前接了阿里云的商业合作后承诺给观众抽奖做 Code Review,最终从知识星球的小伙伴中选出了一位。这位同学的代码之前也曾被 review 过,这次 Review 的项目是一个简单的 AWS S3 文件查看和下载工具(GitHub: `randomseed42/s3_reader`)。整个项目基于 `pyproject.toml` 打包,代码体量不大,结构清晰,包含 `__init__.py`、`__main__.py` 以及核心实现 `s3_reader.py`。高天指出,虽然项目通过 `pyproject.toml` 配置了 CLI 入口 `s3r` 指向 `main` 函数,但如果能在包内直接提供一个模块级入口(如 `__main__.py`)配合 `python -m` 运行会更加规范。

```mermaid flowchart LR accTitle: CLI 命令结构与语法约束 accDescr: 展示 s3r 命令行工具解析子命令并从语法上使用 if elif else 约束分支的流程

subgraph CLI["CLI 命令解析 (argparse)"] direction TB ROOT["s3r"] --> LIST["s3list\n(-b, -p)"] ROOT --> DOWN["s3download\n(-b, -k, -o)"] end

subgraph Branch["分发执行逻辑"] direction TB IF["if args.func == 's3list'"] ELIF["elif args.func == 's3download'"] ELSE["else: assert False / raise"] IF -. 互斥.-> ELIF ELIF --> ELSE end

CLI --> Branch ```

进入 `main.py` 的参数解析部分,该工具使用 `argparse` 的 `Subparsers` 实现了两个子命令:`s3list`(列出文件)和 `s3download`(下载文件)。高天肯定了使用子命令的设计,但在具体的参数定义上指出了几处需要改进的细节:

- **显式声明必须参数**:无论是列出还是下载文件,S3 的存储桶(`bucket`)都是不可或缺的。高天强调:“当你想列出或者下载文件的时候,你是肯定要给一个 bucket 对不对?那么对于这种 required argument,我们最好在增加 argument 的时候直接给他写上,也就是在这里增加一个 `required=True`。”同样地,`-p` / `--prefixes` 虽然配置了 `nargs='+'`,但这仅仅表示传参时后面必须跟一个以上的值,并不代表该参数本身不能省略,因此必须显式加上 `required=True`。 - **使用 choices 限制输入集合**:对于参数 `--output-format`,它显然只能是字典或字节流(`dict` 或 `byte`),高天建议:“如果这里你是一个选择题而不是填空题的话,一个好的习惯是加上 choices,这样如果他给的 output format 不对,你就能很快发现,而不是可能运行了很多程序才意识到‘哎,这个 output format 我并不认识’。” - **从语法约束替代逻辑约束**:在根据 `args.func` 分发执行两个子命令时,原代码连续写了两个独立的 `if`。从业务逻辑上看,两个子命令天然互斥,但高天主张用语法上的互斥性代替纯逻辑互斥——将第二个分支改成 `elif`,并补上兜底的 `else: assert False`。高天解释道,这不仅能节省微小的条件判断开销,更重要的是让读代码的人一目了然:这两个分支是严格互斥的。万一后续开发者在维护时把子命令拼写打错,程序命中 `else` 就会立即抛出断言错误,极大缩短排查 bug 的时间。

[05:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=300s) S3 List 分页循环重构:十行变两行与生成器优化

进入业务实现模块 `s3_reader.py`,高天首先称赞了环境变量读取与 AWS 客户端创建的结构,随后重点剖析了分页获取对象列表的函数。原函数使用 `while True` 循环调用 `client.list_objects_v2`,通过判断返回值中是否存在 `ContinuationToken` 来不断向 `keys` 列表中累加对象名,直到没有更多数据时 `break` 退出。

高天敏锐地指出了这里的一系列性能与可读性优化点。首先是向结果集添加数据的操作:原代码采用了 `keys += [...]` 的列表推导式形式。在 Python 中,`list +=` 实际上等同于 `list.extend()`,并不会像字符串拼接那样重新创建整个列表;然而,原代码中列表推导式内部依然创建了一个包含多达 1000 个元素的临时列表,再通过 `+=` 拼接。这种写法完全可以用生成器表达式(Generator Expression)结合 `keys.extend(...)` 替代,避免无谓的中间内存分配。

随后,高天深入重构了整个分页 `while` 循环结构。原代码中为了处理首次请求(没有 token)和后续翻页请求(带有 token),在循环内部使用了冗长且不易阅读的分支判断。高天指出,只要合理利用字典的 `.get()` 设置默认值,并引入海象运算符(Walrus Operator `:=`),整个循环体可以被极大浓缩:

- 利用 `.get("Contents", [])` 避免在没有内容时抛出键不存在异常。 - 依据 AWS API 规则,`NextContinuationToken` 通常仅在分页未结束时出现,通过海象运算符直接在循环条件中捕获该 token:`while token:= response.get("NextContinuationToken"):`。 - 将进入循环前的首次请求与后续带有 token 的请求以极其紧凑的方式串联,避免了在死循环内部堆叠 `if-else` 和 `break`。

高天总结道:“在做了一些看起来至少是比较合理的假设之后,我们实际上呢就把这个函数的代码浓缩到这么几行了。我相信大家读起来应该也不是非常费劲,就我们并没有牺牲非常多的代码可读性。”

[10:00](https://www.youtube.com/watch?v=7EQsUOT3NKY&t=600s) 并发资源管理、异常捕获与模板代码抽取

接下来审查的是使用并发下载的入口函数。代码利用了 `concurrent.futures.ThreadPoolExecutor` 线程池并发处理各个前缀或键对应的任务,并通过 `tqdm` 显示进度条。高天指出,针对 I/O 密集型任务使用线程池是完全正确的选择,但工程健壮性上存在两个关键遗漏:

```mermaid flowchart TD accTitle: 线程池并发处理的防护流程 accDescr: 展示在 ThreadPoolExecutor 架构中补充 try-finally 与 try-except 的资源管理和任务防崩机制

subgraph ClientMgmt["客户端生命周期 (Try-Finally)"] direction TB C1["创建 S3 Client"] --> C2["执行业务逻辑 / 线程池任务"] C2 --> FIN["finally: client.close()"] end

subgraph JobMgmt["Future 异常处理 (Try-Except)"] direction TB F1["job.result() 获取结果"] F1 -->|"网络波动/超时抛出异常"| EX["except Exception:\n记录错误/兜底,防止全部结果丢失"] F1 -->|"执行成功"| SUC["收集/合并文件结果"] end

C2 -. 并发调度.-> JobMgmt ```

第一是经典的资源管理(Resource Management)问题。客户端在函数开头创建,在函数末尾显式调用 `client.close()`。如果在中间执行线程池任务时抛出任何异常,`client.close()` 将被直接跳过。高天强调:“平时编程的时候就注意一下这种 resource management,最最简单的方法其实就是 try-finally……这样就算你外层不是一个简单的脚本,是一个长期运行的程序,你也可以保证每一次这个 client 都被 close 掉。”

第二是极易被忽视的并发任务异常。原代码在遍历 `futures` 时直接调用 `job.result()`,高天提醒:“如果你维护的是一个产品级的程序,你就要意识到这个 `job.result` 是有可能 raise an exception。在这个 `ThreadPoolExecutor` 里面,如果你给的这个任务本身 raise an exception,那么 `job.result` 会 raise 同样的 exception。而你的这个任务是包含网络连接的,任何包含网络连接的任务都有可能因为网络的波动导致一些 exception。那这个地方如果你不处理的话,可能就会导致其他的结果丢失了,所以这个部分呢可以考虑 try-except 保护一下。”

最后,在对比 `download_by_keys` 与前一个入口函数时,高天发现两者的逻辑结构几乎一模一样,纯粹是复制代码后修改了调用的具体函数与结果拼装逻辑。高天告诫大家:“这就是为什么我经常说不要去复制粘贴你的代码。”他建议将线程池管理、进度条渲染、异常兜底和客户端关闭的大框架抽象成通用的辅助函数(Helper Function),避免重复代码导致的维护困难。

在视频尾声,高天给出了整体评价:作为个人日常使用的工具脚本,这段代码已经完全够用且足够优秀;但 Code Review 的目的在于以更高标准的生产级要求审视代码,提升对边界异常、并发安全和语法严谨性的敏感度。高天最后向观众呼吁支持并一键三连。

## Mentioned 码农高天, AWS S3, 阿里云, argparse, Python, ThreadPoolExecutor, tqdm, GitHub, randomseed42/s3_reader

## Video description Code Review是提升代码水平最快的方式之一,这期视频我们review一个简单的s3文件查看和下载脚本。

本次repo:https://github.com/randomseed42/s3_reader

[Source](https://www.youtube.com/watch?v=7EQsUOT3NKY)

中文简报

码农高天重构S3脚本 用海象运算与语法约束防范生产级隐患 脚本从个人工具迈向生产级不仅要用海象运算符和生成器精简分页循环,更须通过显式互斥断言、线程异常保护与资源兜底防范静默失败。 高天围绕一个轻量级 S3 工具展开审查,首先指出了命令行参数解析中的规范问题,建议在 argparse 中显式声明必选参数、使用 choices 限制输入并用语法互斥替代逻辑互斥以提早捕获 Bug。随后,他针对 S3 对象分页查询逻辑进行重构,借助生成器表达式、字典 get 默认值及海象运算符,将原本十来行的冗长循环精简至数行。最后,高天重点排查了并发下载场景下的工程健壮性隐患,强调必须使用 try-finally 确保客户端资源可靠释放,对并发任务的 job.result 进行异常保护,并提炼模板代码以消除低级的复制粘贴。 码农高天针对开源项目 s3_reader 进行生产级 Code Review,覆盖 argparse 规范、S3 分页循环压缩与并发资源管理等核心问题。 (0:00) 利用生成器表达式与海象运算符重构 S3 分页请求逻辑,可在不牺牲可读性的前提下将冗长的分页循环压缩至两行核心代码。 (5:00) 使用 ThreadPoolExecutor 进行并发 I/O 操作时,必须引入 try-finally 确保客户端资源释放,并对 job.result() 进行 try-except 兜底以防网络异常丢失其余结果。 (10:00)

Reading summary

码农高天针对开源项目 s3_reader 进行 Code Review,指出作为日常脚本该代码已足够可用,但要达到生产级标准,需在 CLI 约束、分页循环与内存分配、并发异常防护以及代码复用四个维度全面强化。 CLI 参数定义与分支互斥的语法约束 (0:00) 在命令行参数解析模块,高天分析了原代码在使用 argparse 时的细节疏漏。存储桶参数(bucket)在业务上不可或缺,却未显式声明 required=True;而带有 nargs='+' 的参数仅代表传参时需提供一个以上的值,并不等于该参数本身不可省略,同样需要显式添加 required 限制。针对输出格式等受限输入,应当配置 choices 建立白名单约束,尽早拦截非法入参,避免运行时进入深层逻辑后才报错。 在分发执行子命令时,原代码使用了两个平行的独立 if 分支。高天主张用语法互斥代替纯逻辑互斥:将第二个分支改为 elif,并在最后设置兜底的 else: assert False。这一调整既能在语法层面明确表达子命令间的绝对互斥关系,又能在未来开发维护中若出现拼写错误时立即暴露断言异常,大幅缩减定位 bug 的成本。 分页循环浓缩与生成器内存优化 (5:00) 在遍历 S3 对象列表的函数中,原实现采用了列表推导式配合 += 向结果集追加元素。高天指出,尽管 Python 中 list += 实质等同于 list.extend(),但列表推导式内部仍然会生成包含多达 1000 个元素的临时列表,带来无谓的中间内存开销,应当替换为生成器表达式与 extend 方法的组合。 对于原本依靠冗长 if-else 和 break 控制的分页 while 循环,高天展示了利用语法特性进行精简的重构思路。通过调用字典的 .get("Contents", []) 规避键缺失风险,并结合海象运算符(:=)在循环条件中直接捕获 response.get("NextContinuationToken"),将带有复杂分支判断的死循环大幅浓缩为两三行紧凑代码,且未牺牲关键的可读性。 并发任务异常防护与客户端生命周期管理 (10:00) 针对使用 ThreadPoolExecutor 进行并发下载的实现,高天肯定了其处理 I/O 密集型任务的合理性,但指出了两项关键的生产级防护缺失。首先是客户端资源的生命周期管理:原代码在函数头部创建客户端并在末尾显式关闭,一旦并发逻辑中间出现异常,close 操作将被跳过,应引入 try-finally 结构确保异常情况下依然能可靠释放连接。 其次是在遍历并发任务结果时的潜在崩溃风险。原代码直接调用 job.result(),高天提醒该方法在子任务抛出异常时会同步向外抛出。涉及网络通信的任务不可避免地会遇到波动与超时,若缺乏 try-except 保护,单个任务的失败将直接导致整个程序崩溃并丢失其余任务的处理结果。 抽离模板代码以消除并发逻辑冗余 (10:00) 在对比 download_by_keys 与其他下载入口函数时,高天发现两者存在大段高度重复的模板代码,仅仅在执行的具体处理函数和结果收集逻辑上存在微小差异。直接复制粘贴代码不仅降低了维护性,也会让资源管理和异常处理的修复工作成倍繁琐。 高天建议将线程池管理、进度条渲染、并发异常捕获及客户端关闭的公共框架抽象为通用的辅助函数(Helper Function),以统一控制并发调度的生命周期,实现业务逻辑与调度框架的解耦。 高天总结认为,尽管审查的 S3 读取工具在个人日常使用场景下已经完全胜任,但从严格的生产级标准来看,开发者必须对底层语法约束、内存分配细节、并发网络异常及通用逻辑抽象保持高度警惕。

Original transcript

[0:00] 在上次接了阿里云的商丹之后呢,我说给大家发一个福利就是从所有通过我的邀请链接购买阿里云产品的小伙伴里和我知智星球的小伙伴里面抽一位出来做code review不过这个报名情况不甚理想啊最终呢我们选择了一位我们知智星球的小伙伴来代码如果大家之前比较关注这个系列的话啊可能对他的名字有印象因为我们之前review过他的代码我们来看看代表有没有进步啊首先readme的部分做的非常简单这刚刚年我做了一个wrapper对吧,然后可以list和read这个aws的file这里他还以这个Pie Project形式做出来一个包啊也就是说它不是一个单纯的脚本不过呢我们更关注的感觉是代码本身它这个项目代码量不大啊我们可以把它全部看完我们看这个代码结构还比较简单的啊就这个init main跟这个s3reader在这个py project文件里呢他给了一个入口也就是当你运行s3r的时候啊他会运行这个s3reader main当然这里做一个module level入口可能会更好啊也就是在这个文件夹里面呢做一个下弯下弯线main.pi不过这个就是打包跟运行的事儿啊,我们主要还是关注代码引力力非常直观啊没什么好说的这块有个version这边让他一共实现了算五个函数吧一个是may函数入口然后是四个功能函数main.pi的部分主要是做这个argument parsing大家可以看到这里用了这个Subparsers啊也就是在使用的时候呢它实际上是有两个sub command它可以S3R空格S3List或者S3R 空格S3 Download然后每一个sub command 有不同的argument这也是一个比较不错的方式啊大家如果有一个程序啊它有不同类型的功能可以尝试一下这个subparser这里注意一下在add argument的时候他这个 bucket argument 在我的理解里是一个 required argument当你想列出或者下载文件的时候 你是肯定要给一个bucket 对不对那么对于这种required argument,我们最好在增加argument的时候直接给他写上也就是在这里增加一个require等于true那包括下面这行也是一样的n arg=+只代表你定义了这个argument的时候,你需要后面根至少一个argument但是如果你没写require等于true他就不会检查你是不是给了-p或者-prefixes所以在写ArgPars的时候养成好习惯啊所有必须的argument都写上require等于true然后接下来这里 Output Format我记得我在之前做call review的时候讲过这个问题这个output format显然不是any string大家看到后面会知道它应该是一个Dict或者一个Byte如果这里你是一个选择题 而不是填空题的话一个好的习惯是加上choices这样如果他给的 output format不对你就能很快发现而不是可能运行了很多程序才意识到哎这个output format我并不认识好那这两个入口啊他也没有做太复杂的事情就是把得到的东西直接打印出来这个部分涉及到一个语法约束和逻辑约束的问题我们大家知道,这个arxfunk是上面parse的时候使用的哪个subparser?由于我们只增加了两个subparser,一个s3list,一个s3download所以我们知道在逻辑上Argsfunk只有可能是S3 list或者是S3 download也就是说从逻辑上这两个if statement只有可能有一个成立但是比逻辑约束更好的是语法约束当我们已经提前知道这个结构就是exclusive的时候也就是说这几件事儿 只有可能运行一个的时候我们完全可以从鱼法上面去对它做一个约束直白点说就是把这个if换成lif当然了 理论上说他也会稍微快一点点因为当第一个if判断是true的时候它可以少判断一个if但是这种微小的性能提升一般来说并不是我们写程序应该关注的最主要的点我们把这个if换成lif之后,我们就可以从语法上非常直观地意识到这两件事只有一个会运行而不是需要重新去读这个if statement乃至于再往上翻一些代码的逻辑,才能够判断,这两个东西原来本来就不能同时运行这是一个好习惯啊这里一个更好的习惯是再加上一个else case这个else里面可以写的很简单啊比如这里我们就写了简单的assert同样的我们读代码的时候我们可以知道现有的逻辑这个else case是永远不会被触发的但是随着未来代码的更迭,可能结构和内容变得更复杂的时候,这个else有可能会派上用场我们举个最最简单的例子假设我在这个L if case里我这个S3 download不小心多打了一个S在没有下面那个else程 [5:00] Yeah. I'm going to use the process of the process of the process of the process. I may need to spend a little time to find out that I did wrong Because when Arxfunk has fallen into these two things, Your system is not any of the response. If I hit the else's hand, I will very quickly determine the problem. Oh, I'm wrong! This is not a problem with the software itself. It's just a good习惯 In many cases, it can be reduced debug time and less error Well, the part of the part is done, let's look at the real功能 part 前面import的部分做的不错 I mentioned the import how to write, it's completely correct. This is the "环境变量" to the profile name and region name. Craig Klein, there is no more thing to say, the name is very simple. It has 4 features functions From the name you can see It has two functions. The function of the 10th input is a "few" form And the number of the form will be used by the number of the form of the form of the form of the form of the form. Let's see the list. and use the typepint Loop is quite clear. If you didn't get Continuation Token In this list, I don't give it. If you want to make a list of things, you get. Here we go to the content and then, we will keep the content to keys 等到数据拿完的时候 break and finally back to the kiss The idea of the logic is quite clear, so you can read it. Here are some small details. First, this is the key is +=list The first question is, is it += in this case? because we know string is going to be able to create new string So when we need to put a lot of strings together, we always put these strings into a list and join I will tell you List += will just use list extent to the rule So it's not going to build a bunch of lists The list is very similar to the list. But we don't have to keep building keys list we still have a list 就是后面的这个list We still built a list of contents. and then extend the list to the key. That's the list we actually have no need to build it Especially考虑到 this list is not small. He said, he said, he said, "I can't return 1000Kiss." So this part can be written like this. and it becomes a generator. 这样我们就不会建立一个新的list了 But this kind of thing is a bit stupid because it's like +=general is a little bit weird Then you can use K-Extend in the center of generator. It will be more comfortable This part has some of the basic little details It's not a problem, but it's quite clear. I will think, this one, list objects v2, and the list objects v2 It's not a difference between the two of them is not a continuation token That this lease object v2, it can take a continuation token = none? But it may not be. I was looking for a API, it was not very clear. But we can't even put the whole loop in the whole way. Especially in our 46th row, only the first round of the row can use it. After the only one can only be able to carry out the 48th. We will come out from the wire loop. Let's try to change this wire loop. Let's change the code We'll do the first list object. And here we can see the response is a dictionary, it has a get-tile. And this part, we use default arguments for the function of the function. There are times when you have default arguments set up You can have a lot of logic to do. We have a default argument here is a "coin list" 也就是说如果这个response meal不存在这个contents的话 Keys is a column list 当然这个部分的前提是我默认了这个response是一个dictionary If it's a weird way of AWS, we can't do this. 接下来这里我也是默认这个 next continuation token 只有在 is truncated的时候才存在 I think this is a more suitable idea But this is what we will go back to the API of the show If it's like this We can just use the next continuation token, not truncated. 拿到之后,我们这里做一个海箱计算符,然后把它直接放到wild statement里 We don't write this while true loop 而是真的使用这个token去判断 If token is NAND, we will be able to stop it, or this token will be valid token that while loop we do something similar But this time this list object is given a continuation token Then we become an extent. [10:00] 那在我们做了一些看起来至少是比较合理的假设之后我们实际上呢就把这个函数的代码浓缩到这么几行了我相信大家读起来应该也不是非常费劲就我们并没有牺牲非常多的代码可读性大家可以和上面比较一下我个人觉得如果你的python相对熟练的话下面的代码其实是更好读懂的当然这个就见仁见智了把代码写成上面那个样子是完全ok的只不过这个地方我还是建议去查一下这个Next Continuation Token是不是只有在East Truncated的时候存在起码这个部分的逻辑可以变得稍微更简单一点好那我们接着看下面这个真正的入口函数这个函数呢相对来说就更好读了它的核心呢是用一个ThreadPool Executor也就是建立了一个现成池然后把每一个prefix发到一个现成去来完成这里现成池没有任何问题啊因为是一个IO瓶颈的代码下面这里呢把每个任务发上去然后用了一个TODM来跟踪状态整体代码挺好的而且没什么问题但是呢这里也是有一个小细节我们来看一下这个client他在第82行的时候建立了一个client然后在第92行的时候把它close掉这是一个经典的resource management对不对那所有的涉及这种resource management我们都要考虑在我们正常的关闭这个资源之前出错了怎么办当然他这个脚本本身很简单如果出错了脚本整个就停了停了之后呢你这个client是不是close就无所谓对吧但是啊这里如果我们想养成一个更良好的习惯的话就在平时编程的时候就注意一下这种resource management最最简单的方法其实就是try finally也就是把它变成这个样子啊就你也不需要特别复杂的结构try finally在绝大多数情况下就已经足够了这样就算你外层不是一个简单的脚本是一个长期运行的程序你也可以保证每一次这个client都被close掉然后这里除了这个resource management还有另外一个需要处理错误的地方这个地方可能不太明显啊就是一个job.result我们当时默认情况下所有的job都成功了那你知道程序是没有问题对吧或者说作为一个简单的脚本这么写也可以但是如果你维护的是一个产品级的程序你就要意识到这个drop.result是有可能read in exception在这个thread pool executor里面如果你给的这个任务本身read in exception那么drop.esult会raise同样的exception而你的这个任务是包含网络连接的任何包含网络连接的任务都有可能因为网络的波动导致一些exception那这个地方如果你不处理的话可能就会导致其他的结果丢失了所以这个部分呢可以考虑try accept保护一下啊好那我们接下来看这个download by key这里我们就能看到了这个output format要么是dict要么是byte这个代表也不是很难读但是蛮有趣的是他在这个函数里面还真做了一个try except所以你也不能说他从来没有想过程序报错这件事他可能是写到这了他想起来或者是他在测试这个函数的时候发现这话会报错这个部分和我们刚才说妹的那个部分一样我们完全可以从语法上制约它也是第二个变成elif然后后面加一个else可以azert一个东西或者raise exception最后我们再来看一下这个入口函数download by keys我们看一下这个函数有没有觉得非常眼熟是不是跟刚才那个入口函数长得几乎一模一样不同的可能就是这个funk还有拿到这个result之后的处理方式那在这种情况下是不是应该写一个单独的helper function就你完全可以把这两件事抽象一下因为这两件事的大框架是一致的都是你要建立一个现成池去完成一件事而如果你用了helper function而不是把这段程序复制一遍你在做刚才我说的那些修改的时候就只用在一个地方做就你的这个resource management的问题包括这个resource的问题你在一个地方改就全改完了这就是为什么我经常说不要去复制粘贴你的代码好来总结一下我觉得整体来说这段代码的质量是不错的尤其作为一个自用的脚本它已经足够好了自用的脚本最大的特点就是只要你知道它怎么工作就可以了你不需要处理其他人对它奇奇怪怪的使用方式你只需要让它在正确的使用方式下正确的运行足够了只不过我们code review的目的是能帮助大家写出更好的更稳定的更可读的代码所以我们一般会把标准拉的稍微高一点不是以一个自用脚本的标准去要求它所以还是希望大家啊看了这期code review能够有所收获好的这一边这里喜欢大家就点上关注别忘了一键三零