似乎 2 is 2
和 3 is 3
在python中总是为真,并且通常,对整数的任何引用都与对同一整数的任何其他引用相同 . None
(即 None is None
)也是如此 . 我知道这不会发生在用户定义的类型或可变类型上 . 但它有时也会在不可变类型上失败:
>>> () is ()
True
>>> (2,) is (2,)
False
也就是说:空元组的两个独立构造产生对内存中相同对象的引用,但是相同的一个(不可变)元素元组的两个独立构造最终创建两个相同的对象 . 我测试了, frozenset
以类似于元组的方式工作 .
是什么决定了一个对象是在内存中复制还是会有一个包含大量引用的实例?它取决于对象在某种意义上是否是“原子”的?它是否因实施而异?
2 回答
Python有一些类型,它保证只有一个实例 . 这些实例的示例是
None
,NotImplemented
和Ellipsis
. 这些是(根据定义)单例,因此像None is None
这样的东西可以保证返回True
,因为无法创建NoneType
的新实例 .它还提供了几个双重1
True
,False
2 - 所有对True
的引用都指向同一个对象 . 同样,这是因为无法创建bool
的新实例 .以上的东西都是由python语言保证的 . 但是,正如您所注意到的,有一些类型(所有不可变的)存储一些实例以供重用 . 这是语言所允许的,但不同的实现可能会选择使用此容差 - 取决于其优化策略 . 属于此类别的一些示例是小整数(-5 - > 255),空
tuple
和空frozenset
.最后,Cpython
intern
解析期间的某些不可变对象...例如如果您使用Cpython运行以下脚本,您将看到它返回
True
:这看起来很奇怪 . Cpython正在玩的技巧是,每当它构造函数
foo
时,它就会看到包含其他简单(不可变)文字的元组文字 . 而不是创建这个元组(或者它不会因为整个交易是不可变的而改变该对象的危险 . 这对于性能来说是一个很大的胜利,其中相同的紧密循环被一遍又一遍地调用 . 小字符串也被实例化 . 这里真正的胜利是在字典查找中.Python可以做一个(超快速)指针比较,然后在检查哈希冲突时回退到较慢的字符串比较 . 由于python的大部分是 Build 在字典查找上的,所以这可能是一个很大的优化语言整体 .1我可能刚刚写了这个词......但希望你能得到这个想法......
2在正常情况下,你不需要检查对象是否是对True的引用 - 通常你只关心对象是否“真实” - 例如如果some_instance:...将执行分支 . 但是,为了完整起见,我把它放在这里 .
请注意,
is
可用于比较不是单例的事物 . 一个常见的用途是创建一个标记值:要么:
The moral of this story is to always say what you mean. 如果要检查值是否为另一个值,请使用
is
运算符 . 如果要检查值是否等于另一个值(但可能不同),请使用==
. 有关is
和==
(以及何时使用)之间差异的更多详细信息,请参阅以下帖子之一:Is there a difference between
==
andis
in Python?Python None comparison: should I use "is" or ==?
附录
我们声称,他们很乐意尝试衡量我们从所有这些优化中得到的结果(除了在使用
is
运算符时稍微增加一些混乱) .字符串“interning”和字典查找 .
这里's a small script that you can run to see how much faster dictionary lookups are if you use the same string to look up the value instead of a different string. Note, I use the term 1112861 in the variable names -- These values aren' t必然实习(虽然它们可能是) . 我只是用它来表明"interned"字符串是字典中的字符串 .
这里的确切值不应该太大,但在我的计算机上,短字符串显示7个部分中的1个部分更快 . 长字符串几乎快2倍(因为如果字符串有更多字符要比较,字符串比较需要更长的时间) . 这些差异仍然存在 .
元组“实习”
这是一个你可以玩的小脚本:
这个有点时间比较棘手(我很乐意采取任何更好的想法,如何在评论中计时) . 这样做的要点是,平均而言(在我的计算机上),一个元组创建列表的时间大约为60% . 但是,
foo_tuple()
平均占foo_list()
所需时间的40%左右 . 这表明我们确实从这些实习生那里获得了一点加速 . 随着元组变大,节省时间似乎会增加(创建更长的列表需要更长的时间 - 元组"creation"自已创建以来需要不变的时间) .还要注意我称之为“实习” . 实际上并非如此(至少在相同的意义上,字符串是固定的) . 我们可以看到这个简单脚本的不同之处:
我们看到字符串实际上是“interned” - 使用相同文字表示法的不同调用返回相同的对象 . 元组“实习”似乎特定于单行 .
它根据实施而有所不同 .
CPython在内存中缓存一些不可变对象 . 对于像“1”和“2”这样的“小”整数(-5到255,如下面的注释中所述)也是如此 . CPython出于性能原因这样做;小整数通常用于大多数程序中,因此它可以节省内存,只创建一个副本(并且由于整数是不可变的,因此是安全的) .
对于像
None
这样的"singleton"对象也是如此;在任何特定时间都只存在一个None
.其他对象(例如空元组,
()
)可以实现为单例,或者它们可以不是 .通常,您不一定要假设不可变对象将以这种方式实现 . CPython出于性能原因这样做,但其他实现可能没有,CPython甚至可能在将来的某个时候停止这样做 . (唯一的例外可能是
None
,因为x is None
是一种常见的Python习惯用法,可能会在不同的解释器和版本中实现 . )通常你想使用
==
而不是is
. Python的is
运算符不经常使用,除非在检查变量是否为None
时 .