In Python, when are two objects the same?
在python中,似乎
1 2 3 4 | >>> () is () True >>> (2,) is (2,) False |
也就是说:空元组的两个独立构造产生对内存中同一对象的引用,但相同的一个(不可变)元素元组的两个独立构造最终会创建两个相同的对象。我测试了,并且
如何确定一个对象是在内存中被复制,还是将有一个具有大量引用的实例?它是否取决于物体在某种意义上是否是"原子的"?它是否因实施而有所不同?
python有一些类型,它保证只有一个实例。这些实例的例子有
它还提供了一些双顶1
以上这些都是由Python语言保证的。但是,正如您所注意到的,有一些类型(都是不可变的)存储了一些实例以供重用。这是语言所允许的,但是不同的实现可能会选择使用或不使用这个允许——这取决于它们的优化策略。属于这类的一些例子是小整数(-5->255),空的
最后,cpython
例如,如果使用cpython运行以下脚本,您将看到它返回
1 2 3 4 5 | def foo(): return (2,) if __name__ == '__main__': print foo() is foo() |
这似乎很奇怪。cpython的诀窍是,每当构造函数
我可能只是编造了那个词…但希望你能明白…2在正常情况下,您不需要检查对象是否是对
注意,
1 2 3 4 | sentinel = object() item = next(iterable, sentinel) if items is sentinel: # iterable exhausted. |
或:好的。
1 2 3 4 | _sentinel = object() def function(a, b, none_is_ok_value_here=_sentinel): if none_is_ok_value_here is sentinel: # Treat the function as if `none_is_ok_value_here` was not provided. |
这个故事的寓意是总是说出你的意思。如果要检查某个值是否是另一个值,请使用
- python中的`=`和'is'有区别吗?
- python none比较:我应该使用"is"还是==?
补遗
我们已经讨论了这些cpython实现细节,并且声称它们是优化的。最好是尝试测量我们从所有这些优化中得到什么(除了在使用
下面是一个小脚本,您可以运行它来查看如果使用相同的字符串而不是不同的字符串来查找值,字典查找的速度会有多快。注意,我在变量名中使用术语"interned"——这些值不一定是interned(尽管它们可能是interned)。我只是用它来表示"interned"字符串就是字典中的字符串。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import timeit interned = 'foo' not_interned = (interned + ' ').strip() assert interned is not not_interned d = {interned: 'bar'} print('Timings for short strings') number = 100000000 print(timeit.timeit( 'd[interned]', setup='from __main__ import interned, d', number=number)) print(timeit.timeit( 'd[not_interned]', setup='from __main__ import not_interned, d', number=number)) #################################################### interned_long = interned * 100 not_interned_long = (interned_long + ' ').strip() d[interned_long] = 'baz' assert interned_long is not not_interned_long print('Timings for long strings') print(timeit.timeit( 'd[interned_long]', setup='from __main__ import interned_long, d', number=number)) print(timeit.timeit( 'd[not_interned_long]', setup='from __main__ import not_interned_long, d', number=number)) |
这里的精确值不应该太重要,但在我的电脑上,短字符串显示的速度大约是7分之一。长字符串几乎快了2倍(因为如果字符串有更多的字符要比较,则字符串比较需要更长的时间)。在python3.x上的差异并不是很明显,但它们仍然存在。好的。tuple"实习"
下面是一个小脚本,您可以在其中玩:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import timeit def foo_tuple(): return (2, 3, 4) def foo_list(): return [2, 3, 4] assert foo_tuple() is foo_tuple() number = 10000000 t_interned_tuple = timeit.timeit('foo_tuple()', setup='from __main__ import foo_tuple', number=number) t_list = (timeit.timeit('foo_list()', setup='from __main__ import foo_list', number=number)) print(t_interned_tuple) print(t_list) print(t_interned_tuple / t_list) print('*' * 80) def tuple_creation(x): return (x,) def list_creation(x): return [x] t_create_tuple = timeit.timeit('tuple_creation(2)', setup='from __main__ import tuple_creation', number=number) t_create_list = timeit.timeit('list_creation(2)', setup='from __main__ import list_creation', number=number) print(t_create_tuple) print(t_create_list) print(t_create_tuple / t_create_list) |
这一点时间上有点难(我很高兴能从评论中找到更好的时间观念)。其中的要点是(在我的电脑上),一个元组的创建时间平均比列表长60%。然而,
还要注意,我把这个叫做"实习"。它实际上不是(至少不是在同样的意义上,弦被囚禁)。我们可以在这个简单的脚本中看到不同之处:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def foo_tuple(): return (2,) def bar_tuple(): return (2,) def foo_string(): return 'foo' def bar_string(): return 'foo' print(foo_tuple() is foo_tuple()) # True print(foo_tuple() is bar_tuple()) # False print(foo_string() is bar_string()) # True |
我们看到字符串实际上是"内部的"——使用相同的文字符号的不同调用返回相同的对象。tuple"interning"似乎只针对一行。好的。好啊。
根据实施情况不同。
cpython在内存中缓存一些不可变的对象。这对于1和2这样的"小"整数是正确的(-5到255,如下面的注释所述)。cpython这样做是出于性能方面的考虑;小整数在大多数程序中都是常用的,因此它将内存保存为只创建一个副本(并且是安全的,因为整数是不可变的)。
这也适用于"单体"对象,如
其他对象(如空元组、
一般来说,您不必假定不可变对象将以这种方式实现。CPython这样做是出于性能原因,但其他实现可能不会,而且CPython甚至可能在将来的某个时候停止这样做。(唯一的例外可能是
通常你想用