Python Cookbook 19.18: 可前瞻的迭代器
1 ## {{{ http://code.activestate.com/recipes/577361/ (r1) 2 import collections 3 class peekable(object): 4 """ An iterator that supports a peek operation. 5 6 this is a merge of example 19.18 of python cookbook part 2, peek ahead more steps 7 and the simpler example 16.7, which peeks ahead one step and stores it in 8 the self.preview variable. 9 10 Adapted so the peek function never raises an error, but gives the 11 self.sentinel value in order to identify the exhaustion of the iter object. 12 13 Example usage: 14 15 >>> p = peekable(range(4)) 16 >>> p.peek() 17 0 18 >>> p.next(1) 19 [0] 20 >>> p.isFirst() 21 True 22 >>> p.preview 23 1 24 >>> p.isFirst() 25 True 26 >>> p.peek(3) 27 [1, 2, 3] 28 >>> p.next(2) 29 [1, 2] 30 >>> p.peek(2) #doctest: +ELLIPSIS 31 [3, <object object at ...>] 32 >>> p.peek(1) 33 [3] 34 >>> p.next(2) 35 Traceback (most recent call last): 36 StopIteration 37 >>> p.next() 38 3 39 >>> p.isLast() 40 True 41 >>> p.next() 42 Traceback (most recent call last): 43 StopIteration 44 >>> p.next(0) 45 [] 46 >>> p.peek() #doctest: +ELLIPSIS 47 <object object at ...> 48 >>> p.preview #doctest: +ELLIPSIS 49 <object object at ...> 50 >>> p.isLast() # after the iter process p.isLast remains True 51 True 52 """ 53 sentinel = object() #schildwacht 54 def __init__(self, iterable): 55 self._nit = iter(iterable).next # for speed 56 self._iterable = iter(iterable) 57 self._cache = collections.deque() 58 self._fillcache(1) # initialize the first preview already 59 self.preview = self._cache[0] 60 self.count = -1 # keeping the count, possible to check 61 # isFirst and isLast status 62 def __iter__(self): 63 return self 64 def _fillcache(self, n): 65 """fill _cache of items to come, with one extra for the preview variable 66 """ 67 if n is None: 68 n = 1 69 while len(self._cache) < n+1: 70 try: 71 Next = self._nit() 72 except StopIteration: 73 # store sentinel, to identify end of iter: 74 Next = self.sentinel 75 self._cache.append(Next) 76 def next(self, n=None): 77 """gives next item of the iter, or a list of n items 78 79 raises StopIteration if the iter is exhausted (self.sentinel is found), 80 but in case of n > 1 keeps the iter alive for a smaller "next" calls 81 """ 82 self._fillcache(n) 83 if n is None: 84 result = self._cache.popleft() 85 if result == self.sentinel: 86 # find sentinel, so end of iter: 87 self.preview = self._cache[0] 88 raise StopIteration 89 self.count += 1 90 else: 91 result = [self._cache.popleft() for i in range(n)] 92 if result and result[-1] == self.sentinel: 93 # recache for future use: 94 self._cache.clear() 95 self._cache.extend(result) 96 self.preview = self._cache[0] 97 raise StopIteration 98 self.count += n 99 self.preview = self._cache[0] 100 return result 101 102 def isFirst(self): 103 """returns true if iter is at first position 104 """ 105 return self.count == 0 106 107 def isLast(self): 108 """returns true if iter is at last position or after StopIteration 109 """ 110 return self.preview == self.sentinel 111 112 def peek(self, n=None): 113 """gives next item, without exhausting the iter, or a list of 0 or more next items 114 115 with n == None, you can also use the self.preview variable, which is the first item 116 to come. 117 """ 118 self._fillcache(n) 119 if n is None: 120 result = self._cache[0] 121 else: 122 result = [self._cache[i] for i in range(n)] 123 return result 124 ## end of http://code.activestate.com/recipes/577361/ }}}