【译】Python中的 `!=`与`is not`不一样

翻译:老齐python

与本文内容配套的图书:《跟老齐学Python:轻松入门》《Python大学实用教程》,各大电商平台有售。安全


Python中的is==是不同的。使用is能够比较数字,代码也正常运行。也有人说is==要更快,或者你可能以为它看起来更像Python。然而,重要的是要记住这些运算符的行为并不彻底相同。bash

==用于比较两个对象的值是否相等,而is检查两个变量是否指向内存中的同一个对象。在大多数状况下,这意味着你应该使用==!=,除非与None进行比较。app

在本文中,你将学习:ide

  • 对象相等和同一性的区别是什么
  • 什么时候使用==is比较对象
  • 这些Python运算符的原理是什么
  • 为何使用isis not比较值会致使意外
  • 如何编写自定义的__eq__()类方法来定义相等运算符行为

介绍isis not的应用

isis not用来比较两个对象。在CPython中,比较的是对象的内存地址。Python中的一切都是对象,每一个对象都存储在特定的内存位置, isis not'检查两个变量是否引用内存中的同一个对象。函数

注意: 记住,具备相同值的对象可能存储在不一样的内存地址中。性能

你可使用id() 来检查一个对象的内存地址:学习

>>> help(id)
Help on built-in function id in module builtins:

id(obj, /)
    Return the identity of an object.

    This is guaranteed to be unique among simultaneously existing objects.
    (CPython uses the object's memory address.) >>> id(id) 2570892442576 复制代码

最后一行显示存储内置函数id自己的内存地址。优化

一般,具备相同值的对象在默认状况下具备相同的id。例如,数字-5到256在CPython中被保存,每一个数字都存储在内存中单一且固定的位置,这为经常使用整数节省了内存。ui

你可使用sys.intern()来保存字符串以提升性能,此函数容许你比较它们的内存地址,而不是对字符串里的字符进行逐个比较:

>>> from sys import intern
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b
False
>>> id(a)
1603648396784
>>> id(b)
1603648426160

>>> a = intern(a)
>>> b = intern(b)
>>> a is b
True
>>> id(a)
1603648396784
>>> id(b)
1603648396784
复制代码

变量ab最初指向内存中的两个不一样对象,如它们的不一样id所示。使用intern后,ab则指向内存中的同一对象。在原来的操做中,两个'hello world'分别在新的内存位置建立对象,可是,对一样的字符串执行intern后,后面所建立的字符串所指向的内存地址与第一个'hello world'的内存地址相同。

注意:即便对象的内存地址在任何给定的时间都是惟一的,但这个内存地址在同一代码的不一样运行过程当中是不一样的,而且取决于CPython的版本和运行代码的计算机。

默认状况下,具备intern效果的对象是NoneTrueFalse和简单字符串。请记住,大多数状况下,具备相同值的不一样对象将存储在不一样的内存地址中,这意味着你不该该使用is来比较值。

存储整数

Python将经常使用的值(例如,整数-5到256)默认保存在内存中,从而节省内存开支。下面的代码向你展现了为何只有一些整数具备固定的内存地址:

>>> a = 256
>>> b = 256
>>> a is b
True
>>> id(a)
1638894624
>>> id(b)
1638894624

>>> a = 257
>>> b = 257
>>> a is b
False

>>> id(a)
2570926051952
>>> id(b)
2570926051984
复制代码

最初,ab引用内存中的同一个存储对象,但当它们的值超出经常使用整数的范围(从-5到256)时,它们就存储在不一样的内存地址中。

当多个变量引用同一对象时

用赋值运算符(=)使一个变量等于另外一个变量时,可使这些变量指向内存中的同一对象。这可能会致使可变对象出现意外行为:

>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]

>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]

>>> id(a)
2570926056520
>>> id(b)
2570926056520
复制代码

刚才发生了什么? 你向a 添加了一个新元素,可是如今b也包含了这个元素! 在b = a这一行,设置b指向与a相同的内存地址,这样两个变量就都引用相同的对象。

若是你独立地定义这些列表,那么它们就被存储在不一样的内存地址中,并独立地运行:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
>>> id(a)
2356388925576
>>> id(b)
2356388952648
复制代码

由于ab如今引用内存中的不一样对象,因此更改一个对象不会影响另外一个对象。

==!=比较对象

回想一下,具备相同值的对象一般存储在不一样的内存地址中。若是要检查两个对象是否具备相同的值,而无论它们存储在内存中的位置,使用运算符=!=。在绝大多数状况下,这就是你想作的。

当对象副本相等但不相同时

在下面的示例中,ba的副本(a是可变对象,如列表或字典)。两个变量都有相同的值,但它们将各自存储在不一样的内存地址:

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]

>>> a == b
True
>>> a is b
False

>>> id(a)
2570926058312
>>> id(b)
2570926057736
复制代码

ab 如今存储在不一样的内存地址,所以a is b再也不返回True。可是,a==b返回True,由于两个对象具备相同的值。

相等比较如何起做用

==的魔力体如今该符号左边对象所具备的__eq__()方法中。

这是一个神奇的类方法,每当这个类的一个实例与另外一个对象进行比较时都会调用它。若是未实现此方法,则默认状况下==比较两个对象的内存地址。

做为练习,建立一个继承strSillyString类并实现__eq__() ,以比较此字符串的长度是否与另外一个对象的长度相同:

class SillyString(str):
    # This method gets called when using == on the object
    def __eq__(self, other):
        print(f'comparing {self} to {other}')
        # Return True if self and other have the same length
        return len(self) == len(other)    
复制代码

如今,用'hello world'建立的SillyString实例应该等于用'world hello'建立的实例,甚至等于长度相同的任何其余对象:

>>> # Compare two strings
>>> 'hello world' == 'world hello'
False

>>> # Compare a string with a SillyString
>>> 'hello world' == SillyString('world hello')
comparing world hello to hello world
True

>>> # Compare a SillyString with a list
>>> SillyString('hello world') == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
comparing hello world to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
True
复制代码

固然,对于一个字符串形式的对象来讲,这是愚蠢的行为,但它确实说明了当你使用==比较两个对象时会发生什么。对于!=,则是经过实现特定的__ne__()方法,给出逆响应。

上面的示例还清楚地向你展现了为何更好的作法是用 is来比较None,而不是使用==运算符。is比较的是内存地址,因此它不只更快,并且更安全,由于它不依赖于任何__eq__()方法的逻辑。

比较的比较

根据经验,你应该经常使用==!=,除非与None进行比较:

  • 使用==!=比较对象的相等性。这里,你一般比较两个对象的值。若是要比较两个对象是否具备相同的内容,而不关心它们存储在内存中的位置,则须要下面的作法。
  • 若是要比较对象的惟一标识,请使用isis not。这里,你要比较两个变量是否指向内存中的同一个对象。这些运算符的主要用例是与None进行比较。与使用类方法相比,按内存地址与None进行比较更快、更安全。

具备相同值的变量一般存储在不一样的内存地址中,这意味着你应该使用==!=来比较他们的值。只有当你想检查两个变量是否指向同一个内存地址时,才使用isis not

结论

在本文中,你了解了==!=比较两个对象的值,而isis not比较两个变量是否引用内存中的同一个对象。若是你牢记这一区别,那么应该可以防止代码中出现意外行为。

你还能够看看如何使用sys.intern()来优化字符串的内存使用和比较,尽管Python可能已经在幕后自动为你处理了这一问题。

如今你已经了解了两种比较的幕后操做,能够尝试编写本身的__eq__()方法,这些方法定义了在使用==运算符时如何比较该类的实例。去应用关于Python比较运算符的这些新知识吧!

原文连接:realpython.com/python-is-i…

相关文章
相关标签/搜索