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
Related
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'm trying to accomplish something with Python but I can't find info about this at all on internet so here is my problem.
Having this sample code:
a = 5
b = [a]
I need b[0] to be a pointer reference to a. Currently if I do
b[0] = 7
a.var will still be 5, and if I set value to a b[0], a keeps the same.
I'd like to know if there is any way in Python to store an Object in an array without copying this, so if I modify one I also modify another.
EDIT ONE
I want to clarify a bit better what I'm trying to accomplish so maybe I can find the real answer.
I have a custom class like
class Foo(object):
def __init__(self):
self.var = 7
I have also a list like this:
myList = [Foo()]
My real problem is here:
In some place of a code, I need to do the following assignment:
self.foo = myList[0]
Then when I try to access this foo var, I notice this is never updated from the original instance. So basically that is what I need.
Edit Two
All answers were right. Using id(var) on every step of my assignments I could verify the Object was the same all the time, so the error must be somewhere else. Thanks to everyone for all the help and I hope this post can help someone else in a future.
You already are storing the object, namely the integer 5. What you are not storing is the name-value binding between the name a and the value 5. Your problem is that the operation b[0] = 7 does not modify the object 5; it only modifies the binding between b[0] and its value (from 5 to 7), leaving the values themselves (5 and 7) untouched. In fact, you cannot modify the value 5, since 5 is an integer, and integers in Python are immutable.
Another way to say that is that b[0] = 7 does not modify the object referred to by b[0]; instead, it modifies the object referred to by b. b is a list, and lists are objects that internally keep track of what objects their indices point to. When you do b[0] = 7, this tells b "make your 0 index point at this new object". It does not "notify" 5 (the old value) or 7 (the new value) that they are now pointed to by b[0]. Objects in Python do not know what kinds of references point at them; they only know what they point at (if anything).
What you are asking for is not a way to store an object, but to store a "variable" --- that is, a binding between a name in the local namespace and a value. The answer is that there isn't, in general, a way to do this in Python. There's no construct that will allow you do b = something(a) so that when you later do b[0] = 8, a will change.
If you want to be able to modify the name-value binding, you need to change a, by making it some kind of object whose contents can be changed, instead of just making it a plain variable. For instance, if you do:
a = [5]
b = a
b[0] = 7
Then you will find a has also changed. The same will be true for other kinds of objects as well: if you set a to some other kind of object whose properties can be altered, and then you alter its properties in-place, those changes will show up no matter how you reference the object.
You may find it helpful to read up on how Python variables work, for instance in this article and this one.
Whether you can do this comes down to whether your list elements are immutable or mutable types. Immutable types can't be changed, so when you re-assign a variable that is immutable it's actually creating a new object and labeling it with the same label. Mutable types can be changed, so it's possible to change the underlying object without reassigning it.
For example, say that a is a list, which is a mutable type.
a = [5]
b = [a]
a[0] = 3
print(b)
# [[3]]
a.append('hi')
print(b)
# [[3, 'hi']]
Your test code actually works the way you want it, because objects are mutable.
class Object():
pass
a = Object()
a.var = 5
b = [a]
b[0].var = 7
print(a.var)
# 7
Your other example code should still work, for all the same reasons. When I assign a = l[0], it isn't copying anything, it's just creating an additional reference a to the object that l[0] references.
l = [Object() for _ in range(5)]
l[0].var = 5
a = l[0]
l[0].var = 7
print(a.var)
# 7
You write
a = 5
b = [a]
I need b[0] to be a pointer reference to a.
You can't. You can make a point to a mutable object and change the value for that object:
class Object(object):
'''an object'''
a = Object()
a.value = 5
b = a
b.value = 7
and now a.value returns:
7
Similarly, but with a list this time:
a = [5]
b = a
b[0] = 7
and now a[0] is:
7
I was working with queue in python when I had a error in code even while the code looked very perfect to me but latter when I changed assignment style all of sudden the code started working. The code looked some what like this before.
x=y=Queue()
x.put("a")
x.put("b")
print y.get()
later i changed to this and it started working
x=Queue()
y=Queue()
x.put("a")
x.put("b")
print y.get(10)
why do both code work differently?
Variables in Python are references, or names, not like variables in C etc.
This code:
x=y=Queue()
means "allow the name y to reference an object in memory made by calling Queue(), and allow the name x to reference the object that y is pointing to." This means both variables refer to the same object - as you can verify with id(x) == id(y).
This code:
x=Queue()
y=Queue()
means "allow the name x to reference one object made by Queue(), and allow the name y to reference another object made by Queue()". In this case, id(x) == id(y) is False
This can often bite you:
a = [1,2,3,4,5]
b = a
b.append(6)
print(a)
# [1,2,3,4,5,6] even though we didn't seem to do anything to a!
To get around this, do import copy; b = a.copy(); instead of b = a.
However, this behaviour doesn't occur to immutable objects like integers:
a = 7
a += 1
This doesn't go to the object that a is referencing and change it by adding one, instead it dereferences a from the object 7, and references it to the object representing the previous value of a + 1 (that is to say, 8). This is unlike actions performed on mutable objects, like lists in the previous example - appending to a list does change the object that the variable is referencing.
So we can do this:
a = 7
b = a
a += 1
print(a)
# 8
print(b)
# 7
Your first code is actually equivalent to:
y=Queue()
x=y
x.put("a")
x.put("b")
print y.get()
Which is different than your second example, because Python handles object by reference. After x=y both variables x and y refer to the same object. In your second example you have two independent queues.
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 am trying to figure out whether the following are immutable in Sage (which is built on Python so I believe if it is immutable in python, I believe in most cases it will be immutable in Sage)
Below are objects e, f, g, i
class e: pass
f = e()
g = pi # (g's "type" in Sage is symbolic expression. It's supposed to be 3.1415....etc)
i = lambda x: x*x
I gather that e is a class which means it is mutable (Does an immutable class make sense? Can't all classes be modified?). Since f is an instance of a class, I am guessing it is also mutable since classes are mutable.
Since numbers are immutable in Python, g should be immutable as well since it is a number despite being irrational
Finally i is a function which means it should be mutable?
I'm not quite sure I understand that concept of immutability. What would it mean for a function to be immutable? For a class to be immutable?
e is mutable. You can, for instance, add a new method on the class: e.foo = lambda self,x: x.
f is mutable. You can, for instance, add a new field to this class instance: f.x = 99.
g is immutable. You can't change anything about it.
i is not immutable. You can do all sorts of evil things to it: i.func_code = (lambda x: 123).func_code after which i(10) will be 123 instead of 100. (You can also do more reasonable things to it. After i.__doc__ = "This function returns the square of its argument." you will get a more helpful result from help(i).)
An object is mutable if there's something you can do to the object that changes its possible future behaviour. You can't change the behaviour of 10; you can change the behaviour of a function object, or a class, or a class instance, or a list. (But not a tuple. Once a tuple is made, it stays just as it is for as long as it exists.)
Formally? An object is mutable if it can change value without changing identity.
Lists are mutable, so the value of a particular instance can change over time:
>>> x = orig_x = []
>>> x == []
True
>>> x += [1]
>>> x == [] # The value of x has changed
False
>>> x is orig_x # But the identity remains the same
True
Numbers are immutable, however, so their value can't change. Instead, the variable has to be updated to refer to a completely different object:
>>> x = orig_x = 1
>>> x == 1
True
>>> x += 1
>>> x == 1 # Again, the value of x has changed
False
>>> x is orig_x # But now the identity has changed as well
False
Immutability is an important concept, since knowing that an object's value can't change lets you make certain assumptions about it (for example, dict effectively requires immutable keys and set and frozenset require immutable members, as the value of an object affects how it should be stored in the data structure. If mutable entries were permitted, they may end up being in the wrong place if they are modified after being stored)
Contrary to popular belief, user defined classes that don't override the definition of equality are technically immutable. This is because the default definition of the "value" of a user defined class is just id(self). When an object's value is its identity, there is obviously no way for them to differ over time, and hence the object doesn't quality as "mutable".
Informally? Most people use an intuitive "Can I change it?" definition along the lines of Gareth McCaughan's answer. It's the same basic idea as the formal definition, just using a broader meaning of the term "value" than the technical definition in terms of equality checks.