一个Python3和Python2的range差异

Python 3 中执行100000000 in range(100000001)会比Python 2快的非常多。

$ time python -c "100000000 in range(100000001)"
python -c "100000000 in range(100000001)"  4.00s user 1.47s system 99% cpu 5.527 total
$ time python3 -c "100000000 in range(100000001)"
python3 -c "100000000 in range(100000001)"  0.04s user 0.01s system 86% cpu 0.055 total
<\>Shell

如果把量级增高, Py2会出现 MemoryError, Py3也会以非常快的速度执行。所以猜测Py3没有生成列表.

先看下两个方法的区别.

Python2

>>> dir(range)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
<\>Python

Python3

>>> dir(range)
['__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index', 'start', 'step', 'stop']
<\>Python

可以看出有部分方法和属性的区别, 很明显Py3增强了 range 函数. 其实需要注意的是__contains__, 这个函数支持了innot in运算符, 所以完全可以通过,判断100000000是否大于开始数字和结束数字即可完成innot in运算.

如何用Py2实现Py3的range

注意代码中的__contains__方法.

class my_range(object):
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            start, stop = 0, start
        self.start, self.stop, self.step = start, stop, step
        if step < 0:
            lo, hi = stop, start
        else:
            lo, hi = start, stop
        self.length = ((hi - lo - 1) // abs(step)) + 1

    def __iter__(self):
        current = self.start
        if self.step < 0:
            while current > self.stop:
                yield current
                current += self.step
        else:
            while current < self.stop:
                yield current
                current += self.step

    def __len__(self):
        return self.length

    def __getitem__(self, i):
        if i < 0:
            i += self.length
        if 0 <= i < self.length:
            return self.start + i * self.step
        raise IndexError('Index out of range: {}'.format(i))

    def __contains__(self, num):
        if self.step < 0:
            if not (self.stop < num <= self.start):
                return False
        else:
            if not (self.start <= num < self.stop):
                return False
        return (num - self.start) % self.step == 0
<\>Python

整理翻译于StackoverFlow


𝔔