当前位置: 首页 > 后端技术 > Python

爬虫(18)如何通过反编译理解for循环(10)

时间:2023-03-26 18:53:20 Python

请继续关注我,点击上方蓝字关注我们。欢迎关注我的公众号,学习Python前面的基本运算符,比如加减乘除,我们就不说了,我觉得最要说的就是for循环操作符。我们确实需要仔细讨论。记得关注点赞哦。谢谢。在此博客中,我们将讨论Pythonfor循环的工作原理我们将从一组基本示例及其语法开始,然后讨论与for循环关联的else块的用途。然后我们将介绍迭代对象、迭代器和迭代器协议,并学习如何创建自己的迭代对象和迭代器之后,我们将讨论如何使用迭代对象和迭代器来实现for循环,并使用whileloop通过迭代器协议实现for循环逻辑。最后,我们将反编译一个简单的for循环,并逐步介绍Python解释器在执行for循环时所执行的指令,正好满足大家的好奇心。这些有助于理解for循环运行时的内部工作原理。Python的for循环for语句是Python中执行迭代的两个语句之一,另一个是while。如果您对Python中的迭代不是很熟悉,Python中的迭代:for、while、break和continue语句是一个很好的切入点。在Python中,for循环用于遍历迭代对象的所有元素。循环内的语句针对可迭代对象的每个元素项执行一次。我们暂时可以把迭代对象想象成一个对象的集合,我们可以一个一个遍历其中的元素。我们将在下一节中解释迭代器和可迭代对象。一个简单的for循环让我们从一个简单的for循环开始,该循环遍历字符串列表并打印每个字符串。如您所见,循环实际上遍历列表中的每个单词并打印它们。也就是说,在循环的每一次通过中,变量word都被分配为列表中的一个元素,并执行for语句中的代码块。由于列表是元素的有序序列,因此循环也以相同的顺序遍历元素。带有else子句的for循环Python中的for循环可以选择与else子句相关联。else子句中的代码块在for循环完成后执行,即遍历完可迭代对象中的所有元素后。现在让我们看看如何扩展前面的示例以包含else条件(子句)else子句何时适用?您已经注意到else子句在for循环完成后执行。那么else代码块有什么意义呢?for循环后面的语句不也执行了吗?我们经常会遇到这样的情况,当满足某个条件时,for循环中途结束。如果这个条件还没有满足,你想执行另一组语句。我们通常使用布尔类型标签来实现,下面是一个调用结果示例:通过else代码块,我们可以避免使用布尔类型标签_found_item_。让我们看看如何使用else子句重写上述方法。注意,如果for循环中的break语句被触发执行,那么else块就会被跳过,所以else代码块适用于for循环中有break语句的情况,我们要执行一些中断条件未触发时的语句。否则,与else关联的语句只会在for循环结束时执行。当您查看本文最后一节中的反编译字节码时,您会看到这一点。For循环语法我们已经看过一些简单的示例,所以让我们以for循环的语法结束本节。基本上,对于每个元素,都会执行set_of_statements_1,一旦所有元素都被迭代,控制器将跳转到else代码块执行set_of_statements_2注意,else子句是可选的。如果没有找到else子句,则在遍历所有元素后循环结束,并且将控制权传递给程序Iterablesvs.Iterators之后的语句以迭代对象。现在让我们尝试了解Python中的可迭代对象是什么。在Python中,可迭代对象是指任何可以在for循环中迭代的对象。这意味着当这个对象作为参数传递给iter()方法时,应该返回一个迭代器。让我们看一下Python中内置迭代的一些常用示例。可以看到,当我们在一个可迭代对象上调用iter()时,它返回的是一个迭代器对象iterator那么什么是迭代器呢?迭代器在Python中被定义为表示流数据的对象。基本上,如果我们将一个对象传递给内置的_next()_方法,它应该从与其关联的流数据中返回下一个值。一旦遍历了所有元素,它将抛出_*StopIteration*异常。对_next()_方法的后续调用也将抛出*StopIteration*_异常。让我们用一个列表试试看。迭代器也是可迭代对象!但是……要记住的一件有趣的事情是_iterators_还支持(_iterator协议的强制支持_)_iter()_方法。这意味着我们可以在迭代器上调用_iter()_方法并获得它自己的迭代器对象,这样我们就可以在需要迭代器的任何地方使用它。例如for循环,需要注意的是,在list这样的容器对象上调用iter()每次都会返回不同的迭代器,而在迭代器上调用iter()只会返回同一个迭代器,所以如果你需要执行多次迭代,并用迭代器替换普通容器或可迭代对象,然后第二次您将看到一个空容器在列表上迭代两次请注意,这与我们期望的列表迭代器迭代两次一样。请注意迭代器在第一次循环时已经结束。我们第二次看到的是一个空的容器迭代器协议。我们在上一篇文章中看到过:1.一个可迭代对象作为参数传递给iter()方法返回一个迭代器。2.一个迭代器,1.当作为参数传递给_next()_方法时返回其下一个元素,或者在遍历所有元素时抛出_StopIteration_异常。2.作为参数传递给_iter()_方法迭代器协议只是一种将对象定义为迭代器的标准方法。我们已经在上一节中看到了该协议的实际应用。根据约定,迭代器应该定义如下两个方法:1.__next__()1.每次调用这个方法,它应该返回迭代器的下一个元素。一旦元素全部遍历完,应该会抛出_StopIteration_异常2.当我们调用内置函数_next()_时,实际上内部调用了这个方法2.__iter__()1.这个方法返回迭代器本身2.When当我们调用内置函数_iter()_,其实我们内部调用的是自己写一个迭代器。知道了迭代协议的原理之后,我们就可以编写自己的迭代器了。我们先来看一个例子。接下来,我们根据给定的范围和步长创建一个Range类。让我们看看它在for循环中是如何工作的。请注意,Range类的实例是一个迭代器和一个可迭代对象。自己写一个。迭代对象我们还可以基于Range迭代器创建一个可迭代对象。它的作用是每当调用__iter()__方法时返回一个新的迭代器。在这里,它应该返回一个新的Range对象。在for循环中使用我们的RangeIterable。for循环是如何工作的现在我们知道什么是迭代器和可迭代对象,让我们看看for循环是如何工作的,看看前面的例子。当我们执行上面的代码块时,会发生以下事情:1.for语句内部,列表["You","are","awesome!"]调用了iter()方法,返回结果是一个迭代器2.然后在迭代器上调用next()方法并将其返回值赋给变量word3。之后,for循环内关联的语句块。在此示例中,打印了word4。将重复步骤2和3,直到next()方法抛出StopIteration。一旦next()抛出StopIteration,controller会跳转到else子句(如果存在),执行else关联的语句块注意:如果在第3步中,for循环语句遇到break语句,则跳过else代码块使用while语句实现for循环逻辑我们可以使用while语句来实现while循环之前的逻辑如下行为其实和for循环是一样的,上面的代码会有如下输出DecompiledforloopInthis本节我们将反编译for循环,并在执行for循环时单步执行解释器的指令。这里_dis_模块用于反编译for循环。具体来说,我们将使用dis.dis方法生成更具可读性的字节码。我们将使用到目前为止我们一直在使用的简单for循环示例。接下来将文件写入文件for_loop.py我们可以调用dis.dis方法来得到可读性高的字节码。在终端上运行以下命令。反编译输出的每一列表示如下:1.第一列:代码行数2.第二列:如果是跳转指令,则有一个“>>”符号3.第三列:字节码偏移量,以字节为单位4.第四列:字节码指令本身5.第五列:显示指令的参数。如果括号中有内容,它只是转换参数以获得更好的可读性现在让我们逐步查看反编译的字节码并尝试了解实际发生了什么1.第1行,即“forwordin[“You”,“are”,“awesome!]:"转换为:0SETUP_LOOP28(to30)该语句将for循环中的代码块压入堆栈。此代码块将跨越28个字节,直到“30”,这意味着,如果for循环中有break语句,则控制器将跳转到偏移量“30”。注意遇到break语句时else代码块是如何被跳过的2LOAD_CONST0(('You','are','awesome!'))接下来,列表被压入栈顶(TOS,andTOS后面用到栈顶或栈顶元素)4GET_ITER这条指令实现了“TOS=iter(TOS)”。这意味着从列表中获取一个迭代器(当前为TOS),然后将迭代器推送到TOS6FOR_ITER12(到20)此指令获取TOS,作为当前迭代器,如果next()方法产生,则调用next()方法一个值,作为TOS入栈,并执行“8STORE_NAME”这条惊天指令一旦next()表示迭代器遍历完毕(即抛出StopIteration异常),TOS(迭代器)就会出栈,字节码计数器增加12。这意味着控制器跳转到指令“20POP_BLOCK”8STORE_NAME0(word)该指令执行转换word=TOS,即_next()_返回的值分配给变量_word_2。第1行,即_"print(word)"_翻译成:10LOAD_NAME1(print)将可调用方法_print_压入栈12LOAD_NAME0(word)将栈中的_word_作为参数压入_print_14CALL_FUNCTION1使用位置调用带参数的函数就像我们在指令中看到的那样,与函数关联的参数出现在TOS中。在获取这对可调用对象(如_print_)之前,会弹出所有遇到的参数。一旦获取到可调用对象,所有的参数都会被传递给它,然后这个可调用对象就会被执行。执行完成后,会将返回值压入TOS,这里是None16POP_TOPTOS(栈顶元素),即函数返回值出栈(出栈)18JUMP_ABSOLUTE6此时,字节码计数器为“6”,表示下一条指令将执行“6FOR_ITER”。循环遍历迭代器中的元素是这样的注意一旦遍历完迭代器中的元素,指令“6FOR_ITER”会结束循环,跳转到“20POP_BLOCK”20POP_BLOCKPOP_BLOCK会从代码的栈中移除block由“0SETUP_LOOP”设置的代码块3.注意第3行(对应_else_)不关联任何特殊指令。程序控制器会依次执行下一条与_else_相关的指令4.第4行,即_"print("Seeyoulater!__")"_翻译成:22LOAD_NAME1(print)pushrelatedto_print_24LOAD_CONST1('Seeyoulater!')将可调用函数的参数对象压入栈中26CALL_FUNCTION1将可调用函数及其参数从栈中弹出,然后函数执行返回值压入TOS28POP_TOPTOS(栈顶元素),即函数返回值(此处为None)从栈中移除5.下面两条指令只是将脚本的返回值(None)加载入栈,并返回30LOAD_CONST2(无)32RETURN_VALUE哦!现在我们已经看到了for循环的反编译指令。希望这有助于更好地理解for循环的工作原理