我的理解是,实际上是an object type in Python 3的range()
函数会动态生成其内容,类似于生成器。
在这种情况下,我希望下面这一行占用的时间过多,因为为了确定1万亿是否在这个范围内,必须生成一个万亿值:
1000000000000000 in range(1000000000000001)
此外:似乎无论我加上多少个零,计算或多或少都需要相同的时间(基本上是瞬时的)。
我也试过这样的方法,但计算还是很快的:
1000000000000000000000 in range(0,1000000000000000000001,10) # count by tens
如果我尝试实现自己的范围函数,结果就不太好了!!
def my_crappy_range(N):
i = 0
while i < N:
yield i
i += 1
return
这个range()
对象在引擎盖下做什么使它如此快速?
之所以选择Martijn Pieters' answer是因为它的完整性,但也请参见abarnert's first answer以获得关于range
在Python 3中成为成熟的序列意味着什么的良好讨论,以及有关跨Python实现的__contains__
函数优化的潜在不一致性的一些信息/警告。abarnert's other answer提供了一些更详细的信息,并为那些对Python 3中优化背后的历史感兴趣的人提供了链接(Python 2中缺少对xrange
的优化)。答案by poke和by wim为感兴趣的人提供了相关的C源代码和解释。
使用source,卢克!
在CPython中,
range(...).__contains__
(方法包装器)最终将委托给一个简单的计算,该计算检查值是否可能在范围内。之所以速度如此之快,是因为我们使用了关于边界的数学推理,而不是直接迭代range对象。为了解释使用的逻辑:start
和stop
之间,以及例如,
994
在range(4, 1000, 2)
中,因为:4 <= 994 < 1000
,和(994 - 4) % 2 == 0
。完整的C代码包含在下面,由于内存管理和引用计数的详细信息,这会更详细一些,但基本思想是这样的:
这个想法的“肉”在the line中提到:
最后请注意-查看代码段底部的
range_contains
函数。如果精确的类型检查失败,那么我们不使用所描述的聪明算法,而是使用_PySequence_IterSearch
返回到范围的哑迭代搜索!您可以在解释器中检查这种行为(我在这里使用的是v3.5.0):这里的基本误解在于认为
range
是一个生成器。不是的。实际上,它不是任何类型的迭代器。你可以很容易地看出:
如果它是一个生成器,重复一次就会耗尽它:
实际上,
range
是一个序列,就像一个列表。你甚至可以测试一下:这意味着它必须遵循作为一个序列的所有规则:
a
range
和alist
的区别在于arange
是一个惰性的或动态的序列;它不记得它的所有值,它只记得它的start
、stop
和step
,并根据需要在__getitem__
上创建值。(顺便说一下,如果您
print(iter(a))
,您会注意到range
使用与list
相同的listiterator
类型。这是怎么回事?一个listiterator
没有使用任何关于list
的特殊功能,除了它提供了一个__getitem__
的C实现,所以它也可以为range
工作。)现在,没有什么说
Sequence.__contains__
必须是恒定时间的,事实上,对于像list
这样的序列的明显例子来说,不是的,但是没有什么说它不能是。而且,实现range.__contains__
只需数学检查((val - start) % step
,但处理负步骤需要一些额外的复杂性)比实际生成和测试所有值要容易得多,那么为什么不应该用更好的方式来实现呢?但是语言中似乎没有任何东西可以保证这会发生。正如Ashwini Chaudhari指出的那样,如果给它一个非整数值,而不是转换成整数并进行数学测试,那么它将返回到迭代所有值并逐个进行比较。而且正是因为CPython 3.2+和pypy3.x版本碰巧包含了这种优化,而且这显然是一个好主意,而且很容易做到,所以IronPython或newkickasspython3.x没有理由不考虑它。(事实上,CPython 3.0-3.1并不包括它。)
如果
range
实际上是一个生成器,就像my_crappy_range
,那么这样测试__contains__
是没有意义的,或者至少它的意义是不明显的。如果您已经迭代了前3个值,那么1
仍然是in
生成器吗?对1
的测试是否应该导致它迭代并使用1
(或最大到第一个值>= 1
)的所有值?Python 3
range()
对象不会立即生成数字;它是一个智能序列对象,可以按需生成数字。它包含的只是开始、停止和步进值,然后当您在对象上迭代时,每次迭代都会计算下一个整数。该对象还实现^{} hook ,如果您的数字是其范围的一部分,将计算。计算是一个O(1)常数时间运算。不需要扫描范围内所有可能的整数。
从^{} object documentation :
所以至少,您的
range()
对象可以:这仍然缺少一些真正的
range()
支持的东西(例如.index()
或.count()
方法、散列、相等测试或切片),但应该会给您一个想法。我还将
__contains__
实现简化为只关注整数测试;如果给一个真正的range()
对象一个非整数值(包括int
的子类),则会启动一个慢扫描以查看是否存在匹配,就像对所有包含值的列表使用包含测试一样。这样做是为了继续支持其他数值类型,这些数值类型恰好支持整数的相等测试,但不希望也支持整数算术。请参阅实现包含测试的原始Python issue。相关问题 更多 >
编程相关推荐