我的理解是,range()
函数实际上是 Python 3 中的一种对象类型,它动态生成其内容,类似于生成器。
在这种情况下,我预计下一行将花费大量时间,因为为了确定1万亿(10**15
)是否在范围内,必须生成1万亿值:
1_000_000_000_000_000 in range(1_000_000_000_000_001)
此外:似乎无论我添加多少个零,计算或多或少都需要相同的时间(基本上是瞬时的)。
我也尝试过类似的方法,但计算仍然几乎是即时的:
# count by tens
10**21 in range(0, 10**21+1, 10)
如果我尝试实现我自己的范围函数,结果就不太好了!
def my_crappy_range(N):
i = 0
while i < N:
yield i
i += 1
return
range()
对象在引擎盖下做什么使它如此快速?
Martijn Pieters的答案因其完整性而被选中,但也请参阅abarnert的第一个答案,以很好地讨论范围
在Python 3中成为完整序列的含义,以及有关python实现中__contains__
函数优化的潜在不一致性的一些信息/警告。abarnert的另一个答案更详细地介绍了一些细节,并为那些对Python 3中优化背后的历史感兴趣的人提供了链接(以及Python 2中缺乏xrange
的优化)。通过戳和wim的答案为那些感兴趣的人提供了相关的C源代码和解释。
匿名用户
使用来源,卢克!
在CPython中,< code >范围(...).__contains__(一个方法包装器)最终将委托给一个简单的计算,该计算检查值是否可能在范围内。这里速度的原因是我们使用了关于边界的数学推理,而不是range对象的直接迭代。为了解释所使用的逻辑:
- 检查该数字是否介于
开始
和停止
之间,以及
- 检查步幅值是否没有“步过”我们的数字。
例如,< code>994在< code >范围(4,1000,2)内,因为:
< li > <代码> 4
完整的C代码包含在下面,由于内存管理和引用计数的详细信息,它有点冗长,但基本思想是存在的:
static int
range_contains_long(rangeobject *r, PyObject *ob)
{
int cmp1, cmp2, cmp3;
PyObject *tmp1 = NULL;
PyObject *tmp2 = NULL;
PyObject *zero = NULL;
int result = -1;
zero = PyLong_FromLong(0);
if (zero == NULL) /* MemoryError in int(0) */
goto end;
/* Check if the value can possibly be in the range. */
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
if (cmp1 == -1)
goto end;
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
}
else { /* negative steps: stop < ob <= start */
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
}
if (cmp2 == -1 || cmp3 == -1) /* TypeError */
goto end;
if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
result = 0;
goto end;
}
/* Check that the stride does not invalidate ob's membership. */
tmp1 = PyNumber_Subtract(ob, r->start);
if (tmp1 == NULL)
goto end;
tmp2 = PyNumber_Remainder(tmp1, r->step);
if (tmp2 == NULL)
goto end;
/* result = ((int(ob) - start) % step) == 0 */
result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
end:
Py_XDECREF(tmp1);
Py_XDECREF(tmp2);
Py_XDECREF(zero);
return result;
}
static int
range_contains(rangeobject *r, PyObject *ob)
{
if (PyLong_CheckExact(ob) || PyBool_Check(ob))
return range_contains_long(r, ob);
return (int)_PySequence_IterSearch((PyObject*)r, ob,
PY_ITERSEARCH_CONTAINS);
}
评论行中提到了这个想法的“实质”:
/* positive steps: start <= ob < stop */
/* negative steps: stop < ob <= start */
/* result = ((int(ob) - start) % step) == 0 */
最后,请查看代码段底部的range_contains
函数。如果精确类型检查失败,则我们不使用所描述的聪明算法,而是使用<code>_PySequence_IterSearch
>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
... pass
...
>>> x_ = MyInt(x)
>>> x in r # calculates immediately :)
True
>>> x_ in r # iterates for ages.. :(
^\Quit (core dumped)
这里根本的误解在于认为范围
是发电机。事实并非如此。事实上,它不是任何类型的迭代器。
你可以很容易地看出这一点:
>>> a = range(5)
>>> print(list(a))
[0, 1, 2, 3, 4]
>>> print(list(a))
[0, 1, 2, 3, 4]
如果它是一个生成器,迭代一次就会耗尽它:
>>> b = my_crappy_range(5)
>>> print(list(b))
[0, 1, 2, 3, 4]
>>> print(list(b))
[]
< code>range实际上是一个序列,就像一个列表。你甚至可以测试这个:
>>> import collections.abc
>>> isinstance(a, collections.abc.Sequence)
True
这意味着它必须遵循序列的所有规则:
>>> a[3] # indexable
3
>>> len(a) # sized
5
>>> 3 in a # membership
True
>>> reversed(a) # reversible
<range_iterator at 0x101cd2360>
>>> a.index(3) # implements 'index'
3
>>> a.count(3) # implements 'count'
1
范围
和列表
之间的区别在于范围
是惰性或动态序列;它不会记住它的所有值,它只记住它的<code>开始,<code>停止,和<code>步骤,并根据需要在<code>上创建值。
(作为补充说明,如果您打印(iter(a))
,您会注意到范围
使用与列表
相同的listiterator
type。这是如何工作的?listiterator
不使用任何关于list
,除了它提供了__getitem__
的C实现之外,因此它也适用于范围
现在,没有什么说
Sequence。__contains__
必须是恒定时间——事实上,对于像list
这样明显的序列示例,它不是。但是没有什么说它不可能是。实现范围更容易。__contains__
只是用数学方法检查它((val-start)%step
,但是有一些额外的复杂性来处理负步骤),而不是实际生成和测试所有的值,那么为什么它不应该用更好的方法来做呢?
但是语言中似乎没有任何东西可以保证这种情况会发生。正如Ashwini Chaudhari所指出的,如果你给它一个非整数值,而不是转换为整数并进行数学测试,它会退回到迭代所有值并一个接一个地比较它们。仅仅因为CPython 3.2和PyPy 3. x版本碰巧包含了这种优化,而且这是一个明显的好主意并且很容易做到,没有理由让IronPython或NewKickAssPython 3. x不能忽略它。(事实上,CPython 3.0-3.1不包括它。)
如果<code>range1
是否仍然是生成器中的?对
1
的测试是否会导致它迭代并消耗1
之前的所有值(或之前的第一个值)
Python 3range()
对象不会立即生成数字;它是一个智能序列对象,可以按需生成数字。它只包含您的开始、停止和步长值,然后当您迭代对象时,每次迭代都会计算下一个整数。
该对象还实现object.__contains__
钩子,并计算您的数字是否属于其范围。计算是一个(接近)常数时间操作 *。永远不需要扫描范围内所有可能的整数。
从< code>range()对象文档中:
与常规的< code>list或< code>tuple相比,< code>range类型的优势在于,range对象将始终占用相同(少量)的内存,而不管它表示的范围大小(因为它只存储< code>start 、< code>stop和< code>step值,根据需要计算单个项目和子范围)。
因此,您的 range()
对象至少可以执行以下操作:
class my_range:
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, step = stop, start, -step
else:
lo, hi = start, stop
self.length = 0 if lo > hi else ((hi - lo - 1) // 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('my_range object index out of range')
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
这仍然缺少真正的range()
支持的几个东西(例如. index()
或. count()
方法、散列、相等性测试或切片),但应该会给你一个想法。
我还简化了__contains__
实现,只关注整数测试;如果您给一个真实的range()
对象一个非整数值(包括int
的子类),将启动一个慢速扫描来查看是否有匹配,就像您对所有包含值的列表使用包含测试一样。这样做是为了继续支持其他数字类型,这些类型恰好支持整数相等测试,但预计也不支持整数算术。请参阅实现包含测试的原始Python问题。
*几乎恒定的时间,因为Python整数是无界的,因此数学运算也会随着N的增长而随时间增长,因此这是一个O(log N)运算。由于它都是在优化的C代码中执行的,并且Python将整数值存储在30位的块中,因此在看到由于此处涉及的整数大小而造成的任何性能影响之前,您已经耗尽了内存。
当我试图匹配一个case语句中的字符串“3”时,如果范围上升到“9”而不是“10”,它就匹配了。 我在猜测它和三重等于运算符有关系,但我不知道它能在范围内,却不匹配的确切原因。 下面是一个IRB运行,它记录了工作(使用'9')和不工作(使用'10')的两种情况: 输出: 输出: 我用作预期结果参考的方法是 和查看转换为数组时输出的是()。 “大小写相等”()运算符的结果让我大吃一惊。
问题内容: 据我了解,该函数实际上是Python 3中的一种对象类型,它像生成器一样动态生成其内容。 在这种情况下,我本以为下一行会花费过多的时间,因为要确定1个四舍五入是否在范围内,必须生成一个四舍五入值: 此外:似乎无论我添加多少个零,计算多少都花费相同的时间(基本上是瞬时的)。 我也尝试过这样的事情,但是计算仍然是即时的: 如果我尝试实现自己的范围函数,结果将不是很好! 使物体如此之快的物体
问题内容: 为什么 初始空间 大于“ 10”的“ 2”? 我在latin1和utf8英语归类中都尝试过: 我知道它与类型有关,因为当它被强制转换时,它可以按预期工作: 这里到底发生了什么? 编辑: 以上所有操作都是在Fedora 13中使用8.4.8完成的。但是我只是在Centos 6中使用9.04进行了测试,结果相同: 数据库清单 新编辑: 这是为了进一步混淆: 问题答案: 我认为Postgre
当我跑的时候 在shell中,它返回。为什么?它应该是?当我跑的时候 它返回。
我刚开始编码。我想对同一个变量使用两次switch语句,我被告知要这样做,变量必须是'inscope'。 作为一个初学者,我不知道那是什么意思。那么在范围内意味着什么呢?而且,如果一个变量不在作用域中,我如何使它在作用域中?
在下面的代码中 为什么当console.log(x)时,x是未定义的?