似乎2 is 2
和3 is 3
在python中始终为真,并且通常,对整数的任何引用都与对同一整数的任何其他引用相同。同样发生在None
(即None is None
)。我知道不会发生在用户定义的类型或可变类型中。但它有时也会在不可变类型上失败:
>>> () is ()
True
>>> (2,) is (2,)
False
即:空元组的两个独立构造产生对内存中相同对象的引用,但是相同的一个(不可变)元素元组的两个独立构造最终创建两个相同的对象。我测试过,frozenset
的工作方式类似于元组。
是什么决定一个对象是在内存中复制还是会有一个包含大量引用的实例?它取决于对象在某种意义上是否是“原子”的?它是否因实施而异?
答案 0 :(得分:38)
Python有一些类型,它保证只有一个实例。这些实例的示例包括None
,NotImplemented
和Ellipsis
。这些是(根据定义)单例,因此None is None
之类的内容可以保证返回True
,因为无法创建NoneType
的新实例。
它还提供了几个双重 1 True
,False
2 - 对True
的所有引用都指向同一个宾语。同样,这是因为无法创建bool
的新实例。
以上的东西都是由python语言保证的。但是,正如您所注意到的,有一些类型(所有不可变的)存储一些实例以供重用。这是语言所允许的,但不同的实现可能会选择使用此容差 - 取决于其优化策略。属于此类别的一些示例是小整数(-5 - > 255),空tuple
和空frozenset
。
最后,Cpython intern
解析期间的某些不可变对象......
e.g。如果您使用Cpython运行以下脚本,您将看到它返回True
:
def foo():
return (2,)
if __name__ == '__main__':
print foo() is foo()
这似乎真的奇怪。 Cpython正在玩的技巧是,无论何时构造函数foo
,它都会看到包含其他简单(不可变)文字的元组文字。而不是一遍又一遍地创建这个元组(或它的等价物),python只创建它一次。由于整个交易是不可改变的,因此没有改变该对象的危险。对于性能而言,这可能是一个巨大的胜利,同时一遍又一遍地调用相同的紧密循环。小字符串也被实习。这里真正的胜利是在字典查找中。 Python可以执行(非常快速)指针比较,然后在检查哈希冲突时回退到较慢的字符串比较。由于python的大部分都是基于字典查找构建的,因此这对整个语言来说可能是一个很大的优化。
1 我可能刚刚提出这个词......但希望你能得到这个想法......
2 在正常情况下,您不需要检查对象是否是对True
的引用 - 通常您只关心是否对象是" truthy" - 例如如果if some_instance: ...
将执行分支。但是,为了完整起见,我把它放在这里。
请注意,is
可用于比较不是单身人士的事物。一个常见的用途是创建一个标记值:
sentinel = object()
item = next(iterable, sentinel)
if items is sentinel:
# iterable exhausted.
或者:
_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.
这个故事的寓意是永远说出你的意思。如果你想检查一个值是否另一个值,那么使用is
运营商。如果要检查值是否等于另一个值(但可能不同),请使用==
。有关is
和==
之间差异的详细信息(以及何时使用),请参阅以下帖子之一:
我们已经讨论了这些CPython实施细节,并且我们声称他们已经进行了优化。尝试衡量我们从所有这些优化中得到的结果是非常好的(除了在使用is
运算符时稍加混乱)。
这是一个小脚本,如果您使用相同的字符串查找值而不是其他字符串,您可以运行该脚本以查看字典查找的速度。注意,我使用术语" interned"在变量名中 - 这些值不一定是实例(尽管它们可能是)。我只是用它来表示"实习" string 是字典中的字符串。
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个部分中的1个部分更快。 long 字符串的速度提高了近2倍(因为如果字符串有更多要比较的字符,字符串比较会花费更长的时间)。这些差异在python3.x上并不那么引人注目,但它们仍然确实存在。
这是一个可以玩的小脚本:
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%。但是,foo_tuple()
平均占foo_list()
所占时间的约40%。这表明我们确实从这些实习生那里获得了一点加速。随着元组变大,节省时间似乎会增加(创建更长的列表需要更长的时间 - 元组"创建"自创建以来需要不变的时间)。
另请注意,我已将此称为#34;实习生"。它实际上并不是(至少不是在相同意义上字符串被中断)。我们可以看到这个简单脚本的区别:
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
我们看到字符串确实是" interned" - 使用相同文字表示法的不同调用返回相同的对象。元组"实习"似乎特定于一行。
答案 1 :(得分:21)
根据实施情况而有所不同。
CPython在内存中缓存一些不可变对象。对于"小"整数,如1和2(-5到255,如下面的评论中所述)。 CPython出于性能原因这样做;小整数通常用在大多数程序中,因此它可以节省内存,只创建一个副本(并且因为整数是不可变的,所以是安全的。)
"单身"也是如此。像None
这样的对象;在任何给定的时间内只存在一个None
。
其他对象(例如空元组,()
)可以实现为单例,也可以不是。
通常,您不一定假设将以这种方式实现不可变对象。 CPython出于性能原因这样做,但其他实现可能没有,CPython甚至可能在将来的某个时候停止这样做。 (唯一的例外可能是None
,因为x is None
是一种常见的Python习惯用语,可能会在不同的解释器和版本中实现。)
通常您希望使用==
代替is
。 Python的is
运算符并不经常使用,除非检查变量是否为None
。
答案 2 :(得分:0)
在Python(尤其是CPython实现)中,对象可以通过以下两种方式之一相同:
现在让我们进行一个案例研究:
您可能在Python中知道所有东西都是一个对象,假设我们有以下代码:
my_var1 = 10
my_var2 = my_var1
现在,在代码的第一行实际发生的事情是,我们创建了一个名为“ my_var1”的变量名,以用作对内存中对象地址的“ 引用”,让我们假设它是“ 0x1000,此对象的值为10。如下:
在第二行代码中,我们创建了一个名为“ my_var2”的新变量,用作对“ my_var1”的引用。该变量已经是对内存地址为0x1000的内存中对象的引用。因此,“ my_var1”和“ my_var2”都具有相同的内存地址和相同的值
现在我们处理完案件之后,他们现在是否认为相同?
是运算符的作用是,它检查两个被测试的变量是否具有相同的内存地址,它不在乎仅在乎的值内存地址,它们共享或不共享相同的内存地址,在我们的情况下为True。
另一方面, ==运算符只关心值,它不在乎内存地址,它关心的是值,在我们的情况下这也是真的
现在让我们进行另一个案例研究(在我们具有相同值和不同内存地址的情况下):
假设以下代码行:
a = [1, 2, 3]
b = [1, 2, 3]
print("a is b",a is b) # False
print("a == b",a == b) # True
a和b都是具有相同值[1、2、3]的列表,但是它们具有不同的内存地址,“这就是为什么使用.append方法来在名为a的列表中添加“ 10”,则名为b的列表完全不受影响,因此它们肯定是不同的对象。”这就是 ==运算符返回 True 的原因,而 is运算符返回 False 的原因。
要获取任何对象(实际上是Python中的任何对象)的内存地址,您可以使用:
id(object_name) #returns the memory address of the object as integer
hex(id(object_name)) #returns the memory address of the object as hexadecimal
希望这能回答您的问题。