gpt4 book ai didi

python - 在 Python 中,两个对象何时相同?

转载 作者:IT老高 更新时间:2023-10-28 21:47:22 25 4
gpt4 key购买 nike

看来2 is 23 is 3在 python 中总是正确的,一般来说,对整数的任何引用都与对同一整数的任何其他引用相同。同样发生在 None (即, None is None )。我知道这不会发生在用户定义的类型或可变类型上。但它有时也会在不可变类型上失败:

>>> () is ()
True
>>> (2,) is (2,)
False

也就是说:空元组的两个独立构造产生对内存中同一个对象的引用,但是相同的单(不可变)元素元组的两个独立构造最终会创建两个相同的对象。我测试过了, frozenset s 的工作方式类似于元组。

是什么决定了一个对象是否会在内存中被复制,或者是否会有一个带有大量引用的单个实例?它是否取决于对象是否在某种意义上是“原子的”?它是否因实现而异?

最佳答案

Python 有一些类型,它保证只有一个实例。这些实例的示例是 None , NotImplemented , 和 Ellipsis .这些(根据定义)是单例,诸如 None is None 之类的东西保证退换True因为没有办法创建 NoneType 的新实例.

它还提供一些双吨 1 True , False 2 -- 所有对 True 的引用指向同一个对象。同样,这是因为无法创建 bool 的新实例。 .

以上都是python语言的保证。但是,正如您所注意到的,有些类型(都是不可变的)存储了一些实例以供重用。这是语言允许的,但不同的实现可能会选择使用或不使用此允许 - 取决于它们的优化策略。属于这一类的一些例子是小整数 (-5 -> 255),空 tuple和空 frozenset .

最后,Cpython intern在解析过程中某些不可变对象(immutable对象)...

例如如果您使用 Cpython 运行以下脚本,您将看到它返回 True :

def foo():
return (2,)

if __name__ == '__main__':
print foo() is foo()

这看起来真的很奇怪。 Cpython 正在玩的技巧是,无论何时构造函数 foo ,它看到一个包含其他简单(不可变)文字的元组文字。与其一遍又一遍地创建这个元组(或其等价物),python 只创建一次。由于整个交易是不可变的,因此该对象没有被更改的危险。在一遍又一遍地调用相同的紧密循环的情况下,这对于性能来说可能是一个巨大的胜利。小字符串也被实习。真正的胜利在于字典查找。 Python 可以进行(极快的)指针比较,然后在检查哈希冲突时退回到较慢的字符串比较。由于 Python 的大部分内容都是建立在字典查找上的,因此这对于整个语言来说是一个很大的优化。

1我可能刚刚编造了这个词......但希望你能明白......

2一般情况下,不需要检查对象是否是对 True的引用。 -- 通常你只关心对象是否“真实”——例如如果 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运算符(operator)。如果你想检查一个值是否等于另一个值(但可能不同),那么使用 == .详细了解 is之间的区别和 == (以及何时使用哪个),请参阅以下帖子之一:
  • Is there a difference between `==` and `is` in Python?
  • Python None comparison: should I use "is" or ==?


  • 附录

    我们已经讨论了这些 CPython 实现细节,我们声称它们是优化。尝试衡量我们从所有这些优化中得到的东西会很好(除了在使用 is 运算符时会增加一些困惑)。

    字符串“实习”和字典查找。

    这是一个小脚本,如果您使用相同的字符串而不是不同的字符串来查找值,您可以运行该脚本来查看字典查找的速度有多快。请注意,我在变量名称中使用了术语“interned”——这些值不一定是 interned(尽管它们可能是)。我只是用它来表示“interned”字符串是字典中的字符串。
    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 上没有那么明显,但它们仍然存在。

    元组“实习”

    这是一个你可以玩的小脚本:
    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%左右需要。这表明我们确实从这些实习生那里获得了一些加速。节省的时间似乎随着元组变大而增加(创建更长的列表需要更长的时间——元组“创建”需要恒定的时间,因为它已经被创建了)。

    另请注意,我称其为“实习生”。它实际上不是(至少不是在字符串被实习的同一意义上)。我们可以在这个简单的脚本中看到不同之处:
    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

    我们看到字符串实际上是“内嵌的”——使用相同文字符号的不同调用返回相同的对象。元组“实习”似乎特定于一行。

    关于python - 在 Python 中,两个对象何时相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36898917/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com