在Python中,“is”关键字可能与相等运算符等价的类型

4 投票
3 回答
646 浏览
提问于 2025-04-16 01:05

在Python中,对于某些类型,is运算符看起来和==运算符是一样的。例如:

>>> 1 is 1
True
>>> "a spoon" is "a spoon"
True
>>> (1 == 1) is (2 == 2)
True

不过,这并不总是成立:

>>> [] == []
True
>>> [] is []
False

这对于可变类型,比如列表,是有道理的。但是,对于不可变类型,比如元组,它们似乎也表现出相同的行为:

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

这引出了几个问题:

  1. ==is的相似性和不可变性有关系吗?
  2. 上面的行为是有规定的,还是只是实现细节?
  3. 最重要的是(也是最基本的),我怎么知道一个赋值操作是会创建对象的副本,还是只是引用它?

更新:如果赋值总是通过引用,那为什么下面的代码不打印2呢?:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

为什么这和下面的C语言代码片段不一样呢:

int a = 1;
int *b = &a;
a = 2;
printf("%d\n", *b);

抱歉我问的问题有点基础,但我还是Python新手,觉得理解这些很重要。你有什么推荐的阅读材料来帮助理解这些问题吗?

3 个回答

1

如果你之前学过C或C++,那么理解Python中的所有变量其实都是指针会简单一些。所以这句话

 a = 1

大致上可以理解为

 Object *a = new Integer(1);

这里的is运算符是用来检查指针是否相等,而==运算符则是根据对象的类型来进行比较。

这个概念稍微复杂一点的是,如果对象是不可变的(比如整数),为了提高效率,上面的代码实际上更像是

 int *a = getFromCacheOrCreateNewInteger(1);

所以有时候(虽然这是实现的细节),不可变对象可能在逻辑上独立创建的情况下,is也会认为它们是同一个对象(例如1+1 is 2-1,但这并没有保证):

>>> 1+2 is 2+1
True
>>> 99999+1 is 1+99999
False
>>> 

更让人困惑的是,尽管Python中的所有变量都是指针,但Python实际上并没有指针的概念。换句话说,你不能直接传递一个函数来指定某个变量应该存储什么。

要做到这一点,你需要传递一个名称(如果变量是全局的)或者传递一个设置函数(如果变量是局部的)。这并不是一个大问题,因为在大多数情况下,你只想要多个返回值,而Python已经很好地处理了这个问题:

def foo(x):
    return x+1, x-1

a, b = foo(12)

另一个小麻烦是,如果你真的需要传递一个没有名称的局部变量的设置器(例如,列表中的一个元素),那么它不能是一个匿名的lambda,因为赋值是一个语句,而lambda只能有一个表达式。不过你可以为此定义局部函数...

def foo(x, setter):
    setter(x + 1)

def bar():
    mylocal = [1,2,3]

    def setFirst(value):
        mylocal[0] = value

    foo(32, setFirst)

(好吧,我说谎了... 其实可以用lambda value: mylocal.__setitem__(0, value),但这算是一个意外;lambda在Python中是非常不受欢迎的,可能一旦大家发现可以这样做,语言就会增加限制来禁止它 ;-))。

如果你想改变一个有名称的局部变量,在Python 2.x中这是不可能的(但在Python 3.x中可以用nonlocal实现)。

关于何时进行复制,何时只是复制指针的问题,答案非常简单。Python从来不会自动进行复制... 如果你想复制,必须自己明确地去做。这就是为什么你常常会看到这样的代码:

class Polygon:
    def __init__(pointlist):
        self.pointlist = pointlist[:]

这里的[:]表示类实例想要存储传入列表的一个副本,这样如果你用一个点的列表创建一个Polygon实例,然后修改这个列表,几何形状就不会改变。

7

== 和 is 这两个比较方式跟不可变性有关系吗?

没有关系。

你可以看看这个链接 Python 中 '==' 和 'is' 比较字符串时,'is' 有时会失败,为什么?,了解为什么在字符串上它们的表现不同;还有这个链接 Python 中 'is' 操作符在整数上表现得很意外,了解为什么在整数上它们的表现也不同(布尔值也是同样的原因)。

上面这些行为是规定好的,还是实现细节?

是实现细节。

我怎么知道一个赋值操作会生成对象的副本,还是只是引用它?

赋值操作总是通过引用进行的。只有当你明确使用 copy.copy(或者类似的东西)时,才会进行复制。

补充:这里说的“通过引用”并不是指 C++ 中的引用。Python 的赋值会 重新绑定 变量。更像是

// int* a, *b;
a = new int(1);
b = a;
a = new int(2);
printf("%d\n", *b);
10

is 操作符用来检查两个对象是否是同一个,也就是说,它们在内存中的地址是否相同。你也可以用 id() 函数来测试这一点:

>>> a = 1
>>> b = 1
>>> a is b
True
>>> id(a) == id(b)
True

== 操作符则用来检查两个对象的内容是否相等。你可以通过实现 __eq__() 函数来自定义这个比较。简单来说,如果两个不同的列表里面的元素都相同,那么它们在内容上是相等的,但在内存中它们是不同的对象。

像字符串和元组这样的不可变类型,Python 可能会把它们放在一起管理,这样两个相同的字符串对象实际上可能是同一个。但这并不意味着你总是可以用 is 来比较这些类型,下面的例子就说明了这一点:

>>> "foobar" is "foobar"   # The interpreter knows that the string literals are
True                       # equal and creates only one shared object.
>>> a = "foobar"
>>> b = "foobar"
>>> a is b        # "foobar" comes from the pool, so it is still the same object.
True
>>> b = "foo"     # Here, we construct another string "foobar" dynamically that is
>>> b += "bar"    # physically not the same as the pooled "foobar".
>>> a == b
True
>>> a is b
False

在 Python 中,赋值操作总是将对象的引用绑定到变量名上,而不是复制对象。

更新

可以把 Python 的变量想象成指针,这和 C 语言类似:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

大致等同于:

const int ONE = 1;
const int TWO = 2;

int *a = &ONE;
int *b = a;  /* b points to 1 */
a = &TWO;    /* a points to 2, b still points to 1 */

撰写回答