I have been using numpy for quite a while but I stumbled upon one thing that I didn't understand fully:
a = np.ones(20)
b = np.zeros(10)
print(id(a)==id(b)) # prints False
print(id(a), id(b)) # prints (4591424976, 4590843504)
print(id(a[0])==id(b[0])) # prints True
print(id(a[0]), id(b[0])) # prints (4588947064, 4588947064)
print(id(a[0])) # 4588947184
print(id(b[0])) # 4588947280
Can someone please explain the behavior observed in last four print statements? Also, I was aware of the fact that id gives you unique object id actually allocated in the memory but every time I run the last two print statements, I got different id values. Is this the expected behavior?
The short answer is that you should forget about relying on id to try and gain deep insight into the workings of python. Its output is affected by CPython implementation details, peephole optimizations and memory reuse. More often than not id is a red herring. This is especially true with numpy.
In your specific case only a and b exist as python objects. When you take an element, a[0], you instantiate a new python object, a scalar of type numpy.float64. These are new python objects and are thus given a new id, unless the interpreter realizes that you're trying to use this object twice (this is probably what's happening in your middle example, although I do find it surprising that two numpy.float64 objects with different values are given the same id. But the weird magic goes away if you assign a[0] and b[0] to proper names first, so this is probably due to some optimization). It could also happen that memory addresses get reused by the interpreter, giving you ids that have appeared before.
Just to see how pointless id is with numpy, even trivial views are new python objects with new ids, even though for all intents and purposes they are as good as the original:
>>> arr = np.arange(3)
>>> id(arr)
140649669302992
>>> id(arr[...])
140649669667056
And here's an example for id reuse in an interactive shell:
>>> id(np.arange(3))
139775926634896
>>> id(np.arange(3))
139775926672480
>>> id(np.arange(3))
139775926634896
Surely there's no such thing as int interning for numpy arrays, so the above is only due to the interpreter reusing ids. The fact that id returns a memory address is again just a CPython implementation detail. Forget about id.
The only thing you might want to use with numpy is numpy.may_share_memory and numpy.shares_memory.
It's important to note that everything in Python is an object, even numbers and Classes.
You have taken 2 numpy array object and each of contains same values i.e 0.
When you say:
print('id of 0 =',id(0))
a = 0
print('id of a =',id(a))
b = a
print('id of b =',id(b))
c = 0.0
print('id of c =',id(c))
The answer you get something like (your case it's different):
id of 0 = 140472391630016
id of a = 140472391630016
id of b = 140472391630016
id of c = 140472372786520
Hence, integer 0 has a unique id. The id of the integer 0 remains constant during the lifetime. Similar is the case for float 0.0 and other objects.
So in your case a[0] or b[0] object id of zero will remain same until or unless it is alive because both contains 0 as object value.
Each time you print a[0] or b[0] in different line you return it's different identity of object because you triggering it at different line hence different lifetime.
You can try:
print(id(a)==id(b))
print(id(a),id(b))
print(id(a[0])==id(b[0]))
print(id(a[0]),id(b[0]))
The output will be:
False
2566443478752 2566448028528
True
2566447961120 2566447961120
Note that second line will return to you 2 different identity of object of numpy array type because both are different list.
Related
Why the id of float values are different when the same value is assigned to two different variables?
a = 10.20
b = 10.20
print(a is b)
False
print(a is not b)
True
print(id(a), id(b))
2449430561168 2449429859408
There is no "good answer" to questions like this: you're staring at undefined implementation details. is is only meant to test for object identity, not object equality. For immutable objects (like floats), it's wholly undefined whether equality implies identity.
And it can change, depending on context and on which version of Python is in use. Here under the released Python 3.10.1, running in IDLE:
>>> a = 10.20
>>> b = 10.20
>>> a is b
False
>>> if 1:
... a = 10.20
... b = 10.20
... print(a is b)
True
Why do they differ? A wise man once said "there's no good answer to questions like this" ;-)
In fact it's an accident of the current implementation: if a float literal is repeated in a single code block, only one instance of the object is created. That's what happens in the second (if 1:) example. But at the start, each line is compiled separately in its own code block, and nothing about literals appearing in one code block is remembered in the next block. Two different float objects happen to be created for the two instances of the literal 10.20. But that's not defined behavior either: it's just what the implementation happens to do today.
What is defined? After a = b, and regardless of what object b is bound to, a is b must be True. That's the heart of "object identity".
Because the docs say that the ID will be unique, it doesn't matter what the variable holds, the value will always be unique.
This is an integer which is guaranteed to be unique and constant for this object during its lifetime.
https://docs.python.org/3/library/functions.html#id
This is because is keywords tests whether two variables belong to the same object. Here both the int objects are different as they're guaranteed to be unique always.
Use == instead to check of they are equals.
I read the Python 2 docs and noticed the id() function:
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
CPython implementation detail: This is the address of the object in memory.
So, I experimented by using id() with a list:
>>> list = [1,2,3]
>>> id(list[0])
31186196
>>> id(list[1])
31907092 // increased by 896
>>> id(list[2])
31907080 // decreased by 12
What is the integer returned from the function? Is it synonymous to memory addresses in C? If so, why doesn't the integer correspond to the size of the data type?
When is id() used in practice?
Your post asks several questions:
What is the number returned from the function?
It is "an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime." (Python Standard Library - Built-in Functions) A unique number. Nothing more, and nothing less. Think of it as a social-security number or employee id number for Python objects.
Is it the same with memory addresses in C?
Conceptually, yes, in that they are both guaranteed to be unique in their universe during their lifetime. And in one particular implementation of Python, it actually is the memory address of the corresponding C object.
If yes, why doesn't the number increase instantly by the size of the data type (I assume that it would be int)?
Because a list is not an array, and a list element is a reference, not an object.
When do we really use id( ) function?
Hardly ever. You can test if two references are the same by comparing their ids, but the is operator has always been the recommended way of doing that. id( ) is only really useful in debugging situations.
That's the identity of the location of the object in memory...
This example might help you understand the concept a little more.
foo = 1
bar = foo
baz = bar
fii = 1
print id(foo)
print id(bar)
print id(baz)
print id(fii)
> 1532352
> 1532352
> 1532352
> 1532352
These all point to the same location in memory, which is why their values are the same. In the example, 1 is only stored once, and anything else pointing to 1 will reference that memory location.
Rob's answer (most voted above) is correct. I would like to add that in some situations using IDs is useful as it allows for comparison of objects and finding which objects refer to your objects.
The later usually helps you for example to debug strange bugs where mutable objects are passed as parameter to say classes and are assigned to local vars in a class. Mutating those objects will mutate vars in a class. This manifests itself in strange behavior where multiple things change at the same time.
Recently I had this problem with a Python/Tkinter app where editing text in one text entry field changed the text in another as I typed :)
Here is an example on how you might use function id() to trace where those references are. By all means this is not a solution covering all possible cases, but you get the idea. Again IDs are used in the background and user does not see them:
class democlass:
classvar = 24
def __init__(self, var):
self.instancevar1 = var
self.instancevar2 = 42
def whoreferencesmylocalvars(self, fromwhere):
return {__l__: {__g__
for __g__ in fromwhere
if not callable(__g__) and id(eval(__g__)) == id(getattr(self,__l__))
}
for __l__ in dir(self)
if not callable(getattr(self, __l__)) and __l__[-1] != '_'
}
def whoreferencesthisclassinstance(self, fromwhere):
return {__g__
for __g__ in fromwhere
if not callable(__g__) and id(eval(__g__)) == id(self)
}
a = [1,2,3,4]
b = a
c = b
democlassinstance = democlass(a)
d = democlassinstance
e = d
f = democlassinstance.classvar
g = democlassinstance.instancevar2
print( 'My class instance is of', type(democlassinstance), 'type.')
print( 'My instance vars are referenced by:', democlassinstance.whoreferencesmylocalvars(globals()) )
print( 'My class instance is referenced by:', democlassinstance.whoreferencesthisclassinstance(globals()) )
OUTPUT:
My class instance is of <class '__main__.democlass'> type.
My instance vars are referenced by: {'instancevar2': {'g'}, 'classvar': {'f'}, 'instancevar1': {'a', 'c', 'b'}}
My class instance is referenced by: {'e', 'd', 'democlassinstance'}
Underscores in variable names are used to prevent name colisions. Functions use "fromwhere" argument so that you can let them know where to start searching for references. This argument is filled by a function that lists all names in a given namespace. Globals() is one such function.
id() does return the address of the object being referenced (in CPython), but your confusion comes from the fact that python lists are very different from C arrays. In a python list, every element is a reference. So what you are doing is much more similar to this C code:
int *arr[3];
arr[0] = malloc(sizeof(int));
*arr[0] = 1;
arr[1] = malloc(sizeof(int));
*arr[1] = 2;
arr[2] = malloc(sizeof(int));
*arr[2] = 3;
printf("%p %p %p", arr[0], arr[1], arr[2]);
In other words, you are printing the address from the reference and not an address relative to where your list is stored.
In my case, I have found the id() function handy for creating opaque handles to return to C code when calling python from C. Doing that, you can easily use a dictionary to look up the object from its handle and it's guaranteed to be unique.
I am starting out with python and I use id when I use the interactive shell to see whether my variables are assigned to the same thing or if they just look the same.
Every value is an id, which is a unique number related to where it is stored in the memory of the computer.
If you're using python 3.4.1 then you get a different answer to your question.
list = [1,2,3]
id(list[0])
id(list[1])
id(list[2])
returns:
1705950792
1705950808 # increased by 16
1705950824 # increased by 16
The integers -5 to 256 have a constant id, and on finding it multiple times its id does not change, unlike all other numbers before or after it that have different id's every time you find it.
The numbers from -5 to 256 have id's in increasing order and differ by 16.
The number returned by id() function is a unique id given to each item stored in memory and it is analogy wise the same as the memory location in C.
The is operator uses it to check whether two objects are identical (as opposed to equal). The actual value that is returned from id() is pretty much never used for anything because it doesn't really have a meaning, and it's platform-dependent.
The answer is pretty much never. IDs are mainly used internally to Python.
The average Python programmer will probably never need to use id() in their code.
It is the address of the object in memory, exactly as the doc says. However, it has metadata attached to it, properties of the object and location in the memory is needed to store the metadata. So, when you create your variable called list, you also create metadata for the list and its elements.
So, unless you an absolute guru in the language you can't determine the id of the next element of your list based on the previous element, because you don't know what the language allocates along with the elements.
I have an idea to use value of id() in logging.
It's cheap to get and it's quite short.
In my case I use tornado and id() would like to have an anchor to group messages scattered and mixed over file by web socket.
I'm a little bit late and i will talk about Python3. To understand what id() is and how it (and Python) works, consider next example:
>>> x=1000
>>> y=1000
>>> id(x)==id(y)
False
>>> id(x)
4312240944
>>> id(y)
4312240912
>>> id(1000)
4312241104
>>> x=1000
>>> id(x)
4312241104
>>> y=1000
>>> id(y)
4312241200
You need to think about everything on the right side as objects. Every time you make assignment - you create new object and that means new id. In the middle you can see a "wild" object which is created only for function - id(1000). So, it's lifetime is only for that line of code. If you check the next line - you see that when we create new variable x, it has the same id as that wild object. Pretty much it works like memory address.
As of in python 3 id is assigned to a value not a variable. This means that if you create two functions as below, all the three id's are the same.
>>> def xyz():
... q=123
... print(id(q))
...
>>> def iop():
... w=123
... print(id(w))
>>> xyz()
1650376736
>>> iop()
1650376736
>>> id(123)
1650376736
Be carefull (concerning the answer just below)...That's only true because 123 is between -5 and 256...
In [111]: q = 257
In [112]: id(q)
Out[112]: 140020248465168
In [113]: w = 257
In [114]: id(w)
Out[114]: 140020274622544
In [115]: id(257)
Out[115]: 140020274622768
I've been learning python and i've got a doubt and i'm not sure if what i'm thinking is the correct. As you know Python is an OOP Language and all objects have an id, type and a value.
However there's a concept that i'm not sure if i understood well. It's the mutable and immutable objects.
I know there are some objects that are mutables like arrays, lists and there are others that are immutable like strings, ints, tuples etc.
Basically the main diference is that the immutable can't change its value. For example if i've got an int var:
x = 1
its value is always the same.
After that line of code if i create another var x but now = 2, it's another object, because they have different id's, right? But now, how can i access a var by id, for example my first x var?
Hope you can help.
Thanks! :)
But now, how can i access a var by id, for example my first x var?
You can't. When you do:
x = 1
x = 2
when python executes the x = 2 the 1 object you assigned to x doesn't have any references that can reach it and thus you cannot access it in any way. There's no such a thing as get_object_from_id that allows you to reach any object given its id.
When an object doesn't have any reference to it, it is effectively unreachable. When the garbage collector will be run the object will be destroyed (actually, in CPython most of the objects are destroyed immediately, but there isn't any guarantee about when an object will be destroyed).
As Martijn pointed out, the id of an object is just an implementation detail and it may be reused by the interpreter when an object is destroyed, hence, even from a theoretical point of view, a function such as get_object_from_id can't be implemented in any sensible manner.
Also, assignments have nothing to do with mutable vs immutable types.
You can see the difference between mutable and immutable types when you perform operations on them. For example:
some_list.append(1)
Adds 1 to some_list (modifying it), without creating a new list object, while:
a = 1
a += 2
Here a is not modified by increasing its value to 3, but a new integer object of value 3 is assigned to a.
You can use the id to check that the as are different objects:
>>> a = 1
>>> id(a)
25889896
>>> a += 2
>>> id(a)
25889848
Note that relying on id is a really poor choice. For example:
>>> a = (1, 2)
>>> b = (1, 2)
>>> a is b # is compare the ids just like: id(a) == id(b)
False
>>> a[0] is b[0]
True
As you can see the interpreter decided to re-use the same object 1 for two different tuples. This is an optimization used for small integers, that works because integers are immutable.
Bottom line: id can only tell you if two things aren't the same thing at a certain moment in time. Any other use is just wrong.
This seems like more of a question about scope,.. for which I would recommend reading this.
http://docs.python.org/release/1.5.1p1/tut/scopes.html
say we have the following
x = 1
def PrintStuff(x):
print (x)
PrintStuff(2)
>>> 2
In the function PrintStuff, x is a local variable.
Outside the function PrintStuff lies a global variable x
To get the global x, there are multiple options, but for right now lets just use the globals() function, which returns a dictionary containing all global variables.
x = 1
def PrintStuff(x):
print( x)
print( globals()["x"])
PrintStuff(2)
>>> 2
>>> 1
Similarly we could use the locals() function to get the local x
def PrintStuff(x):
print( locals()["x"])
PrintStuff(2)
>>> 2
I read the Python 2 docs and noticed the id() function:
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
CPython implementation detail: This is the address of the object in memory.
So, I experimented by using id() with a list:
>>> list = [1,2,3]
>>> id(list[0])
31186196
>>> id(list[1])
31907092 // increased by 896
>>> id(list[2])
31907080 // decreased by 12
What is the integer returned from the function? Is it synonymous to memory addresses in C? If so, why doesn't the integer correspond to the size of the data type?
When is id() used in practice?
Your post asks several questions:
What is the number returned from the function?
It is "an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime." (Python Standard Library - Built-in Functions) A unique number. Nothing more, and nothing less. Think of it as a social-security number or employee id number for Python objects.
Is it the same with memory addresses in C?
Conceptually, yes, in that they are both guaranteed to be unique in their universe during their lifetime. And in one particular implementation of Python, it actually is the memory address of the corresponding C object.
If yes, why doesn't the number increase instantly by the size of the data type (I assume that it would be int)?
Because a list is not an array, and a list element is a reference, not an object.
When do we really use id( ) function?
Hardly ever. You can test if two references are the same by comparing their ids, but the is operator has always been the recommended way of doing that. id( ) is only really useful in debugging situations.
That's the identity of the location of the object in memory...
This example might help you understand the concept a little more.
foo = 1
bar = foo
baz = bar
fii = 1
print id(foo)
print id(bar)
print id(baz)
print id(fii)
> 1532352
> 1532352
> 1532352
> 1532352
These all point to the same location in memory, which is why their values are the same. In the example, 1 is only stored once, and anything else pointing to 1 will reference that memory location.
Rob's answer (most voted above) is correct. I would like to add that in some situations using IDs is useful as it allows for comparison of objects and finding which objects refer to your objects.
The later usually helps you for example to debug strange bugs where mutable objects are passed as parameter to say classes and are assigned to local vars in a class. Mutating those objects will mutate vars in a class. This manifests itself in strange behavior where multiple things change at the same time.
Recently I had this problem with a Python/Tkinter app where editing text in one text entry field changed the text in another as I typed :)
Here is an example on how you might use function id() to trace where those references are. By all means this is not a solution covering all possible cases, but you get the idea. Again IDs are used in the background and user does not see them:
class democlass:
classvar = 24
def __init__(self, var):
self.instancevar1 = var
self.instancevar2 = 42
def whoreferencesmylocalvars(self, fromwhere):
return {__l__: {__g__
for __g__ in fromwhere
if not callable(__g__) and id(eval(__g__)) == id(getattr(self,__l__))
}
for __l__ in dir(self)
if not callable(getattr(self, __l__)) and __l__[-1] != '_'
}
def whoreferencesthisclassinstance(self, fromwhere):
return {__g__
for __g__ in fromwhere
if not callable(__g__) and id(eval(__g__)) == id(self)
}
a = [1,2,3,4]
b = a
c = b
democlassinstance = democlass(a)
d = democlassinstance
e = d
f = democlassinstance.classvar
g = democlassinstance.instancevar2
print( 'My class instance is of', type(democlassinstance), 'type.')
print( 'My instance vars are referenced by:', democlassinstance.whoreferencesmylocalvars(globals()) )
print( 'My class instance is referenced by:', democlassinstance.whoreferencesthisclassinstance(globals()) )
OUTPUT:
My class instance is of <class '__main__.democlass'> type.
My instance vars are referenced by: {'instancevar2': {'g'}, 'classvar': {'f'}, 'instancevar1': {'a', 'c', 'b'}}
My class instance is referenced by: {'e', 'd', 'democlassinstance'}
Underscores in variable names are used to prevent name colisions. Functions use "fromwhere" argument so that you can let them know where to start searching for references. This argument is filled by a function that lists all names in a given namespace. Globals() is one such function.
id() does return the address of the object being referenced (in CPython), but your confusion comes from the fact that python lists are very different from C arrays. In a python list, every element is a reference. So what you are doing is much more similar to this C code:
int *arr[3];
arr[0] = malloc(sizeof(int));
*arr[0] = 1;
arr[1] = malloc(sizeof(int));
*arr[1] = 2;
arr[2] = malloc(sizeof(int));
*arr[2] = 3;
printf("%p %p %p", arr[0], arr[1], arr[2]);
In other words, you are printing the address from the reference and not an address relative to where your list is stored.
In my case, I have found the id() function handy for creating opaque handles to return to C code when calling python from C. Doing that, you can easily use a dictionary to look up the object from its handle and it's guaranteed to be unique.
I am starting out with python and I use id when I use the interactive shell to see whether my variables are assigned to the same thing or if they just look the same.
Every value is an id, which is a unique number related to where it is stored in the memory of the computer.
If you're using python 3.4.1 then you get a different answer to your question.
list = [1,2,3]
id(list[0])
id(list[1])
id(list[2])
returns:
1705950792
1705950808 # increased by 16
1705950824 # increased by 16
The integers -5 to 256 have a constant id, and on finding it multiple times its id does not change, unlike all other numbers before or after it that have different id's every time you find it.
The numbers from -5 to 256 have id's in increasing order and differ by 16.
The number returned by id() function is a unique id given to each item stored in memory and it is analogy wise the same as the memory location in C.
The is operator uses it to check whether two objects are identical (as opposed to equal). The actual value that is returned from id() is pretty much never used for anything because it doesn't really have a meaning, and it's platform-dependent.
The answer is pretty much never. IDs are mainly used internally to Python.
The average Python programmer will probably never need to use id() in their code.
It is the address of the object in memory, exactly as the doc says. However, it has metadata attached to it, properties of the object and location in the memory is needed to store the metadata. So, when you create your variable called list, you also create metadata for the list and its elements.
So, unless you an absolute guru in the language you can't determine the id of the next element of your list based on the previous element, because you don't know what the language allocates along with the elements.
I have an idea to use value of id() in logging.
It's cheap to get and it's quite short.
In my case I use tornado and id() would like to have an anchor to group messages scattered and mixed over file by web socket.
I'm a little bit late and i will talk about Python3. To understand what id() is and how it (and Python) works, consider next example:
>>> x=1000
>>> y=1000
>>> id(x)==id(y)
False
>>> id(x)
4312240944
>>> id(y)
4312240912
>>> id(1000)
4312241104
>>> x=1000
>>> id(x)
4312241104
>>> y=1000
>>> id(y)
4312241200
You need to think about everything on the right side as objects. Every time you make assignment - you create new object and that means new id. In the middle you can see a "wild" object which is created only for function - id(1000). So, it's lifetime is only for that line of code. If you check the next line - you see that when we create new variable x, it has the same id as that wild object. Pretty much it works like memory address.
As of in python 3 id is assigned to a value not a variable. This means that if you create two functions as below, all the three id's are the same.
>>> def xyz():
... q=123
... print(id(q))
...
>>> def iop():
... w=123
... print(id(w))
>>> xyz()
1650376736
>>> iop()
1650376736
>>> id(123)
1650376736
Be carefull (concerning the answer just below)...That's only true because 123 is between -5 and 256...
In [111]: q = 257
In [112]: id(q)
Out[112]: 140020248465168
In [113]: w = 257
In [114]: id(w)
Out[114]: 140020274622544
In [115]: id(257)
Out[115]: 140020274622768
This question already has answers here:
Is there a difference between "==" and "is"?
(13 answers)
Closed 2 years ago.
Its been a couple of days since I started learning python, at which point I stumbled across the == and is. Coming from a java background I assumed == does a comparison by object id and is by value, however doing
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a == b
True
Seems like is is equivalent of java's == and python's == is equivalent to java's equals(). Is this the right way to think about the difference between is and ==? Or is there a caveat?
'==' checks for equality,
'is' checks for identity
See also
Why does comparing strings in Python using either '==' or 'is' sometimes produce a different result?
is checks that both operands are the same object. == calls __eq__() on the left operand, passing the right. Normally this method implements equality comparison, but it is possible to write a class that uses it for other purposes (but it never should).
Note that is and == will give the same results for certain objects (string literals, integers between -1 and 256 inclusive) on some implementations, but that does not mean that the operators should be considered substitutable in those situations.
To follow up on #CRUSADER's answer:
== checks the equality of the objects, using the eq method.
is checks the actual memory location of the objects. If they are the same memory location, they test as True
As was mentioned above, the first 2**8 integers are stored in memory locations for speed, so to see whats going on use some other object or integers above 256. For instance:
In [8]: a = 1001
In [9]: b = a # this sets a pointer to a for the variable b
In [10]: a == b
Out[10]: True # of course they are equal
In [11]: a is b
Out[11]: True # and they point to the same memory location
In [12]: id(a)
Out[12]: 14125728
In [13]: id(b)
Out[13]: 14125728
In [14]: b = 1001 #this instantiates a new object in memory
In [15]: a == b
Out[15]: True
In [16]: a is b
Out[16]: False #now the memory locations are different
In [17]: id(a)
Out[17]: 14125728
In [18]: id(b)
Out[18]: 14125824
This is one of those situations where seemingly synonymous concepts might confuse newer programmers, such as I was when I first wrote this answer. You were close with your assumption based on Java, but backwards. The difference between these operators boils down to the matter of object equivalency vs. object identity, but contrary to what you assumed, == compares by value and is compares by object id. From cpython's built-in documentation (as obtained from typing help("is") at my interpreter's prompt, but also available online here):
Identity comparisons
====================
The operators "is" and "is not" test for object identity: "x is y" is
true if and only if x and y are the same object. Object identity
is determined using the "id()" function. "x is not y" yields the
inverse truth value.
To break this down a bit for less experienced programmers (or really anyone that needs a refresher), a rough definition of each concept is given as follows:
object equivalency: two references are equivalent if they have the
same effective value.
object identity: two references are identical if they refer to the same exact object, e.g. same memory location
object equivalency occurs in most of the situations that you might expect, such as if you compare 2 == 2 or [0, None, "Hello world!"] == [0, None, "Hello world!"]. For built-in types, this is usually determined based on the value of the object, but user-defined types can define their own behavior by defining the __eq__ method (though it is still advised to do so in a way that reflects the complete value of the object).
Object identity is something that can lead to equivalence, but is, on the whole, a separate matter entirely. Object identity depends strictly on whether 2 objects (or rather, 2 references) refer to the exact same object in memory, as determined by id(). Some useful notes about identical references: because they refer to the same entity in memory, they will ALWAYS (at least in cpython) have the same value and, unless __eq__ was defined unconventionally, will therefore be equivalent. This even holds if you attempt to change one of the references through an in-place operation, such as list.append() or my_object[0]=6, and care should be taken to test identity and make copies of objects that should be separate (this is one of the main purposes of is: detecting and dealing with aliases). For example:
>>> first_object = [1, 2, 3]
>>> aliased_object = first_object
>>> first_object is aliased_object
True
>>> aliased_object[0]= "this affects first_object"
>>> first_object
['this affects first_object', 2, 3]
>>> copied_object= first_object.copy() #there are other ways to do this, such as slice notation or the copy module, but this is the most simple and direct
>>> first_object is copied_object
False
>>> copied_object[2] = "this DOES NOT affect first_object"
>>> first_object
['this affects first_object', 2, 3]
>>> copied_object
['this affects first_object', 2, "this DOES NOT affect first_object"]
There are a lot of situations that can result in 2 references being aliased, but outside the assignment operator (which always creates a reference to the assigned object, as above), many of them depend on the exact implementation, e.g. not every implementation of Python will intern strings under the same circumstances or preemptively cache (I'm not sure what the proper term is in this case) the same range of integers. My installation of cpython, for instance, seems to have cached -8 on startup when this article seems to imply that's out of the normal range. Thus, even if is seems to work in your dev environment, it's better to be on the same side, avoid inconsistent behavior altogether, and use ==. is should be reserved for situations where you actually want to compare identity.