Python 节省内存的循环写法 (二)

接着上篇。

7 切片筛选

Python 中的普通切片操作,比如:

lis = [1,3,2,1]
lis[:1]

它们的缺陷还是 lis 必须全部载入内存,所以更节省内存的操作 islice,原型如下:

`islice`(_iterable_, _start_, _stop_[, _step_])

应用例子:

In [41]: list(islice('abcdefg',1,4,2))
Out[41]: ['b', 'd']

实现它的大概代码如下:

def islice(iterable, *args):
    s = slice(*args)
    start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
    it = iter(range(start, stop, step))
    try:
        nexti = next(it)
    except StopIteration:
        for i, element in zip(range(start), iterable):
            pass
        return
    try:
        for i, element in enumerate(iterable):
            if i == nexti:
                yield element
                nexti = next(it)
    except StopIteration:
        for i, element in zip(range(i + 1, stop), iterable):
            pass

巧妙利用生成器迭代结束时会抛出异常StopIteration,做一些边界处理的事情。

8 细胞分裂

tee 函数类似于我们熟知的细胞分裂,它能复制原迭代器 n 个,原型如下:
tee(iterablen=2)
应用如下,可以看出复制出的两个迭代器是独立的


a = tee([1,4,6,4,1],2)
In [51]: next(a[0])
Out[51]: 1

In [52]: next(a[1])
Out[52]: 1

实现它的代码大概如下:

def tee(iterable, n=2):
    it = iter(iterable)
    deques = [collections.deque() for i in range(n)]
    def gen(mydeque):
        while True:
            if not mydeque:            
                try:
                    newval = next(it)   
                except StopIteration:
                    return
                for d in deques:     
                    d.append(newval)
            yield mydeque.popleft()
    return tuple(gen(d) for d in deques)

tee 实现内部使用一个队列类型 deques,起初生成空队列,向复制出来的每个队列中添加元素 newval, 同时 yield 当前被调用的 mydeque 中的最左元素。

9 map 变体

starmap 可以看做是 map 的变体,它能更加节省内存,同时 iterable 的元素必须也为可迭代对象,原型如下:

`starmap`(_function_, _iterable_)

应用:

In [63]: list(starmap(lambda x,y: str(x)+'-'+str(y), [('a',1),('b',2),('c',3)]))
Out[63]: ['a-1', 'b-2', 'c-3']

starmap 的实现细节如下:

def starmap(function, iterable):
    for args in iterable:
        yield function(*args)

10 复制元素

repeat 实现复制元素 n 次,原型如下:

`repeat`(_object_[, _times_])

应用如下:

In [66]: list(repeat(6,3))
Out[66]: [6, 6, 6]

In [67]: list(repeat([1,2,3],2))
Out[67]: [[1, 2, 3], [1, 2, 3]]

它的实现细节大概如下:

def repeat(object, times=None):
    if times is None:# 如果times不设置,将一直repeat下去
        while True: 
            yield object
    else:
        for i in range(times):
            yield object

11 笛卡尔积

笛卡尔积实现的效果同下:

((x,y) for x in A for y in B)

所以,笛卡尔积的实现效果如下:

In [68]: list(product('ABCD', 'xy'))
Out[68]:
[('A', 'x'),
 ('A', 'y'),
 ('B', 'x'),
 ('B', 'y'),
 ('C', 'x'),
 ('C', 'y'),
 ('D', 'x'),
 ('D', 'y')]

它的实现细节:

def product(*args, repeat=1):
    pools = [tuple(pool) for pool in args] * repeat
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)

12 加强版 zip

组合值。若可迭代对象的长度未对齐,将根据 fillvalue 填充缺失值,注意:迭代持续到耗光最长的可迭代对象,效果如下:

In [69]: list(zip_longest('ABCD', 'xy', fillvalue='-'))
Out[69]: [('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]

它的实现细节:

def zip_longest(*args, fillvalue=None):
    iterators = [iter(it) for it in args]
    num_active = len(iterators)
    if not num_active:
        return
    while True:
        values = []
        for i, it in enumerate(iterators):
            try:
                value = next(it)
            except StopIteration:
                num_active -= 1
                if not num_active:
                    return
                iterators[i] = repeat(fillvalue)
                value = fillvalue
            values.append(value)
        yield tuple(values)

它里面使用 repeat,也就是在可迭代对象的长度未对齐时,根据 fillvalue 填充缺失值。理解上面代码的关键是迭代器对象 (iter),next 方法的特殊性:
结合这个提示再理解上面代码,就不会吃力。

总结

Python 的 itertools 模块提供的节省内存的高效迭代器,里面实现基本都借助于生成器,所以一方面了解这 12 个函数所实现的基本功能,同时也能加深对生成器 (generator) 的理解,为我们写出更加高效、简洁、漂亮的代码打下坚实基础。

结合这个提示再理解上面代码,就不会吃力.