“is”关键字的类型可能等同于Python中的等于运算符

时间:2010-07-10 07:35:39

标签: python reference identity variable-assignment immutability

对于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. 最重要的(基本上),如何知道作业是否会产生正在制作的对象的副本,或者是否对其进行引用?
  4. 更新: 如果总是通过引用进行分配,为什么以下不打印2?:

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

    为什么这不等同于以下C片段:

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

    对于这个问题的新见事表示抱歉,但我是一个Python新手并且觉得理解这一点很重要。您是否有任何阅读建议您理解这些问题?

3 个答案:

答案 0 :(得分:9)

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中的赋值始终将对象的引用绑定到变量名,并且永远不会隐含副本。

<强>更新

类似于C,认为Python变量始终是指针:

>>> 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 */

答案 1 :(得分:7)

  

== /是否与不变性相关?

没有

请参阅Python ‘==’ vs ‘is’ comparing strings, ‘is’ fails sometimes, why?有关字符串的原因,以及Python “is” operator behaves unexpectedly with integers有关它为什么适用于整数的原因(因此出于同样的原因而起作用)。

  

上面列出的行为或实施细节是什么?

实施细节。

  

我如何知道作业是否会产生正在制作的对象的副本,或者是否会对其进行引用?

作业总是通过参考。只有在明确使用copy.copy(或类似内容)时才会进行复制。

编辑:通过“引用”,我不是指C ++中的引用。 Python的赋值将重新绑定变量。它更像是

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

答案 2 :(得分: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中确实alla变量确实是指针,但Python中没有指针概念,换句话说,没有办法传递一个函数,你的变量应该是什么存储

为此,您需要传递名称(如果变量是全局的)或传递要调用的setter函数(如果变量是本地的)。这并不是一个很大的麻烦,因为在大多数情况下你只需要多个返回值,Python已经很好地处理了这个问题:

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

a, b = foo(12)

另一个额外的烦恼是,如果你真的需要为没有名字的局部变量传递一个setter(例如列表的元素),那么它就不能是匿名lambda,因为赋值是语句< / strong>和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的实例时使用点列表然后修改此列表,然后几何体不会改变。