scapy的实现中,yield的用法很好,它使得loop成为一个生成器,从而使得__iter__返回一个迭代器,那么yield的本质是什么呢?
保有yield的函数返回的是一个迭代器,而返回迭代器的也就是生成器了,用yield构造迭代器将会非常方便,总的来说,设yield函数返回一个迭代器iter1,只有在你显式的调用其next函数或者隐式作for-in操作时,yield函数中的yield值才会依次按照其yield的顺序返回出来,yield函数如果你使用yield N,那么这和return N是区别市很大的,如果仅仅return N的话,这个N就直接被返回了,它仅仅是一个值,而如果是yield N的话,虽然最终还是可以得到的还是N,但是你得到N的方式却变了,你只能通过iter的接口来得到N。yield只是在“塞”住了很多数据,只有iter的接口才能将其一个一个“拔”出来,在一个函数中,只要你yield了一个数据,那么就等于塞住了一个数据,将来需要用iter接口拔出它,比如以下的例子:
def test():
print "333"
yield 3
print "444"
yield 4
print "555"
yield 5
使用命令行运行之:
>>> def test():
... print "333"
... yield 3
... print "444"
... yield 4
... print "555"
... yield 5
...
>>> it=test() #没有任何输出
>>> a=it.next() #a的值是3,并且将输出333,后面的444,555依旧不输出,必须等待下次调用next以及下下次调用
333
>>> print a
3
隐式调用也一样:
>>> it=test()
>>> for k in it:
... print k
... break
...
333
3
>>> it=test()
>>> for r in it:
... print r
...
444
4
555
5
就是这样!有了yield的理论知识,接下来再看scapy的Packet类的__iter__这个函数:
00def __iter__(self):
01 def loop(todo, done, self=self):
02 if todo:
03 eltname = todo.pop()
04 elt = self.__getattr__(eltname)
05 if not isinstance(elt, Gen):
06 if self.fieldtype[eltname].islist:
07 elt = SetGen([elt])
08 else:
09 elt = SetGen(elt)
10 for e in elt:
11 done[eltname] = e
12 for x in loop(todo[:], done):
13 yield x
14 else:
15 if isinstance(self.payload,NoPayload):
16 payloads = [None]
17 else:
18 payloads = self.payload
19 for payl in payloads:
20 done2 = done.copy()
21 for k in done2:
22 if isinstance(done2[k], RandNum):
23 done2[k] = int(done2[k])
24 pkt = self.__class__(**done2)
25 pkt.underlayer = self.underlayer
26 pkt.overload_fields = self.overload_fields.copy()
27 if payl is None:
28 yield pkt
29 else:
30 yield pkt/payl
31 return loop(map(lambda x:str(x), self.fields.keys()), {})
在__str__中调用__iter__().next()返回的Packet实际上只有一个,那就是在13行返回的这个x,这是为何呢?在__iter__中总共有三个地方“塞”住了Packet(事实上可以归为两个,因为28行和30行可以视为一个),分别在13行,28行和30行,在__iter__的执行过程中,首先进入的是前半部分,只有在todo没有的时候才会进入后半部分else,可见if todo段是解决本层Packet对象的,如果todo没有了,在12行调用loop时才会进入到else段,因此else段是递归解决本层Packet对象的payload的,然后在28行或者30行“塞”一个Packet,可是如果执行到了28行或者30行,也塞住了Packet,那么接下来返回到哪里呢?想象一下当初是怎么进来的,是从12行进来的,于是返回12行,返回之后,直接就被“拔”出了,这是因为12行有一个for-in,拔出28行或者 30行塞入的Packet后紧接着又塞入一个,然后如果该层Packet对象的属性(也即todo链表)不止一个,还会进一步的返回上一个todo属性调用的loop,在for-in中又把刚刚在13行塞入的Packet给拔出了,最终__iter__返回的时候,其实只有最后一个Packet对象在13行被塞入。
对于直接调用__str__进而调用__iter__的Packet对象而言,进入else之后28行或者30行的yield被12行的for-in所抵消,最终在13行yield一个Packet对象,也是唯一的一个,对于进入19行的for-in而间接递归调用__iter__的所取得的payl这个Packet对象而言,其在13行最终yield的那个唯一的Packet对象被19行本身的for-in所抵消,这样最后就剩下了直接调用__str__函数的那个Packet对象本身。str函数的不断调用使得包的构建从下往上进行,每次上升一层,因为每次都会以已经处理完的Packet对象的payload再次调用str,从L3PacketSocket的send函数的outs.send中的str开始这一过程,随后在do_build中的p+str(self.payload)中继续这一过程,完成包的构建。
13行的yield x返回两个地方,一个是直接的__iter__().next()的调用,比如__str__中,另一个是隐式的for-in调用,其中也类似一个next的调用,比如19行的for-in,完全和13行的yield x“塞”“拔”抵消,另外12行的for-in,也是完全抵消,然而紧接着在13行又“塞”了一个,这就构成了一个循环,一个递归的循环。12-13行的“塞拔”是塞拔的同一个Packet对象,先拔再塞,拔的是28/29行塞入的对象或者13行塞入的对象,19行拔的是当前Packet对象的payload,而这个payload是在递归到上一层时在13行最后塞入的。懵了吗?递归加迭代就是这么...