在Python中,两个对象什么时候相同? - python

似乎2 is 23 is 3在python中始终为true,通常,对整数的任何引用都与对相同整数的任何其他引用相同。 None(即None is None)也是如此。我知道用户定义类型或可变类型不会发生这种情况。但是有时它在不可变类型上也会失败:

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

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

由什么决定一个对象是要在内存中重复还是要有一个包含大量引用的实例?在某种意义上,它是否取决于物体是否是“原子的”?它会根据实施情况而变化吗?

参考方案

Python具有某些类型,可以保证只有一个实例。这些实例的示例是NoneNotImplementedEllipsis。这些是(按定义)单例,因此像None is None之类的东西一定会返回True,因为无法创建新的NoneType实例。

它还提供了一些doubletons 1 TrueFalse 2-对True的所有引用都指向同一对象。同样,这是因为无法创建bool的新实例。

python语言保证了以上所有内容。但是,您已经注意到,有些类型(全部不可变)存储一些实例以供重用。该语言允许这样做,但是不同的实现可以选择是否使用此配额-取决于其优化策略。属于此类的一些示例是小整数(-5-> 255),空tuple和空frozenset

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

例如如果您使用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运算符。如果要检查一个值是否等于另一个值(但可能不同),请使用==。有关is==(以及何时使用)的区别的更多详细信息,请参阅以下文章之一:

  • Is there a difference between `==` and `is` in Python?
  • Python None comparison: should I use "is" or ==?
  • 附录

    我们已经讨论了这些CPython实现细节,并声称它们是优化。最好只是衡量我们从所有优化中得到的收益(使用is运算符时会产生一些混乱)。

    字符串“interning”和字典查找。

    这是一个小脚本,您可以运行该脚本来查看如果使用相同的字符串而不是其他字符串来查找值,则字典查找的速度将提高多少。请注意,我在变量名称中使用了术语“interned”-这些值不一定是intern(尽管可以)。我只是用它来表示“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
    

    我们看到字符串确实是“interned”的-使用相同文字表示法的不同调用将返回相同的对象。元组“实习”似乎特定于一行。

    为什么使用'=='或'is'比较字符串有时会产生不同的结果? - python

    我有一个Python程序,其中将两个变量设置为'public'值。在条件表达式中,我有比较var1 is var2失败,但如果将其更改为var1 == var2,它将返回True。现在,如果我打开Python解释器并进行相同的“是”比较,则此操作成功。>>> s1 = 'public' >>…

    单行的'if'/'for'语句是否使用Python样式好? - python

    我经常在这里看到某人的代码,看起来像是“单线”,这是一条单行语句,以传统的“if”语句或“for”循环的标准方式执行。我在Google周围搜索,无法真正找到可以执行的搜索类型?任何人都可以提出建议并最好举一些例子吗?例如,我可以一行执行此操作吗?example = "example" if "exam" in exam…

    在返回'Response'(Python)中传递多个参数 - python

    我在Angular工作,正在使用Http请求和响应。是否可以在“响应”中发送多个参数。角度文件:this.http.get("api/agent/applicationaware").subscribe((data:any)... python文件:def get(request): ... return Response(seriali…

    python JSON对象必须是str,bytes或bytearray,而不是'dict - python

    在Python 3中,要加载以前保存的json,如下所示:json.dumps(dictionary)输出是这样的{"('Hello',)": 6, "('Hi',)": 5}当我使用json.loads({"('Hello',)": 6,…

    Python exchangelib在子文件夹中读取邮件 - python

    我想从Outlook邮箱的子文件夹中读取邮件。Inbox ├──myfolder 我可以使用account.inbox.all()阅读收件箱,但我想阅读myfolder中的邮件我尝试了此页面folder部分中的内容,但无法正确完成https://pypi.python.org/pypi/exchangelib/ 参考方案 您需要首先掌握Folder的myfo…