scapy-yield的含义和使用

标签: lambdabuildclass
2114人阅读 评论(0) 收藏 举报

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行最后塞入的。懵了吗?递归加迭代就是这么...

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:6070040次
    • 积分:75805
    • 等级:
    • 排名:第20名
    • 原创:1397篇
    • 转载:2篇
    • 译文:0篇
    • 评论:2928条
    最新评论