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
[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能够有所收获好的这一边这里喜欢大家就点上关注别忘了一键三零