Types for which “is” keyword may be equivalent to equality operator in Python

与世无争的帅哥 提交于 2019-12-18 06:58:12

问题


For some types in Python, the is operator seems to be equivalent to the == operator. For example:

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

However, this is not always the case:

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

This makes sense for mutable types such as lists. However, immutable types such as tuples seem to display the same behavior:

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

This raises several questions:

  1. Is the == / is equivalence related to immutability?
  2. Are the behaviors above specified, or an implementation detail?
  3. Most importantly (and basically), how can I know if an assignment will result in a copy of an object being made, or a reference to it being made?

Update: If assignment is always by reference, why doesn't the following print 2?:

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

Why isn't this equivalent to the following C snippet:

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

Apologies for the newbness of this question, but I am a Python novice and feel that it is important to understand this. Is there any reading you would recommend to understand these sort of issues?


回答1:


The is operator tests if two objects are physically the same, that means if they have the same address in memory. This can also be tested using the id() function:

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

The == operator on the other hand, tests for semantical equality. This can also be overridden by custom classes by implementing the __eq__() function. Semantically, two different lists are equal if their elements are all equal, but physically they will be different objects.

Immutable types like strings and tuples might be pooled by the Python implementation, so that two literal string objects are in fact physically identical. But this does not mean that you can always use is to compare those types, as demonstrated in the following example:

>>> "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

Assignments in Python always bind the reference to an object to a variable name and never implies a copy.

UPDATE

Analogous to C, think of Python variables always being pointers:

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

Roughly equivalent to:

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



回答2:


Is the == / is equivalence related to immutability?

No.

See Python ‘==’ vs ‘is’ comparing strings, ‘is’ fails sometimes, why? on why it works on strings, and Python “is” operator behaves unexpectedly with integers on why it works on integers (thus bools for the same reason).

Are the behaviors above specified, or an implementation detail?

Implementation detail.

how can I know if an assignment will result in a copy of an object being made, or a reference to it being made?

Assignment is always by reference. Copying is done only if you explicitly use copy.copy (or something like that).

Edit: By "by reference" I don't mean references in C++. Python's assignment will rebind the variable. It's more like

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



回答3:


If you come from a C or C++ background then it's probably simpler to rationalize that all variables in Python are indeed pointers. So the statement

 a = 1

is indeed roughly similar to

 Object *a = new Integer(1);

The is operator checks for pointer equality and the == operator instead involves a computation that depends on the type of objects.

A little complication to this scheme is that if the objects are immutable (e.g. an integer) then for efficiency reasons the code above is indeed a bit more like

 int *a = getFromCacheOrCreateNewInteger(1);

so sometimes (but it's an implementation detail) immutable objects may be are the same object for is even if they are created independently from a logical point of view (e.g. may be that 1+1 is 2-1, but no guarantees):

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

To add a bit more of confusion is that even if indeed alla variables in Python are pointers quite surprisingly there is no pointer concept in Python, in other words there is no way to pass a function in which of your variables something should be stored.

To do that you need either to pass a name (if the variable is a global) or to pass a setter function to be called (if the variable is a local). This is not really a big annoyance since in most cases you just want multiple return values and this is handled nicely already by Python:

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

a, b = foo(12)

Another extra annoyance is that if you really need to pass a setter for a local variable without a name (e.g. an element of a list) it cannot be an anonymous lambda because assignment is a statement and lambda is only allowed a single expression. You can however define local functions for that...

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

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

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

    foo(32, setFirst)

(OK. I lied... it's indeed possible to use lambda value: mylocal.__setitem__(0, value) but this is more or less an unwanted incident; lambda is so hated in Python that probably once they discover this is possible another limitation will be added to the language to forbid it ;-) ).

If you want to change a named local instead this is just impossible in Python 2.x (but possible with Python 3.x and nonlocal).

About the question of when copies are performed and when instead just the pointer is copied the answer is very simple. Python never ever makes a copy automatically... if you want to make a copy the you must do it yourself explicitly. This is why for example is common to see code like:

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

The [:] notation means here that the class instance wants to store a copy of the list passed so that if you create an instance of Polygon with a list of points and the later modify this list then the geometry doesn't change.



来源:https://stackoverflow.com/questions/3218308/types-for-which-is-keyword-may-be-equivalent-to-equality-operator-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!