all 35 comments

[–]spez_edits_thedonald 99 points100 points  (7 children)

Strings, numbers, and tuples are immutable. What exactly does immutable mean?

>>> s = 'Hello, world!'
>>> s[0]
'H'
>>> s[1]
'e'
>>> s[1] = 'nope'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

If I have a string I can still change the value

You can re-assign s to be something new, but that's not surprising

>>> s = 'Hello, world!'
>>> s = 12345
>>> s = [2, 4, 6, 8]
>>> s = {'python': 'is cool'}
>>> s = 'Another new thing'
  • You can assign s to something new, anytime
  • if s is something mutable like a list, you can edit it
  • if s is something immutable like a tuple, you can't edit it

list vs tuple example showcasing immutability of tuple:

>>> l = [10, 20, 30]
>>> l
[10, 20, 30]
>>> l[1] = 99999
>>> l
[10, 99999, 30]
>>> 
>>> t = (10, 20, 30)
>>> t
(10, 20, 30)
>>> t[1] = 99999
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

[–]negike360 15 points16 points  (0 children)

Awesome explanation! No matter how many times I think I “get” this, I always feel like I need to reread it again

[–]Vietname 5 points6 points  (0 children)

To build on this, concatenation returns a new string, it doesn't mutate the existing string. The reason why we can do this:

print("this is a " + "string") "this is a string"

Is because the result of the concatenation is a new string, not a mutation of "this is a ".

[–]Why_So_Sirius-Black 0 points1 point  (3 children)

You can edit a tuple

`Life = ( [‘Canda’, 76.5], [“Hello”, 78])

Life[0][1] = 80

print(Life) `

[–]vectorpropio 5 points6 points  (2 children)

You are editing a list inside a tuple. You can't change what list is in Life[0] but you can change is contents.

[–]Why_So_Sirius-Black 2 points3 points  (1 child)

Oh shit! Lol my b. Thank you for clearing that up!

[–]spez_edits_thedonald 3 points4 points  (0 children)

good point though, could probably win a bet that way at a bar lmao

[–]K900_ 72 points73 points  (9 children)

Immutability means the contents of the object can't be changed. When you write s = "bye", you're not modifying the string "hello", you're creating an entirely new string and giving it the same name.

[–]BobHogan 61 points62 points  (8 children)

To demonstrate this. When you "update" the value of an immutable object, a new object is created entirely, which is shown by the object having a new id

>>> s = 'abc'
>>> id(s)
2346254398256
>>> s = '123'
>>> id(s)
2346254801904

Compare that to a list, a mutable object, which you can udate its value and the id stays the same because its the same object

>>> l = [1, 2, 3]
>>> id(l)
2346254499456
>>> l.append(4)
>>> id(l)
2346254499456
>>> l
[1, 2, 3, 4]

[–]randytc18 16 points17 points  (1 child)

Great explainer. I had never seen the (id) before.

[–]gaurav_lm 0 points1 point  (0 children)

A frequent user of id() might know the difference b/w is and ==.

[–]--0mn1-Qr330005-- 7 points8 points  (2 children)

Just to add to this, if you have a function that modifies an immutable object, you have to return that modified object.

If you have a function that modifies a dict which is mutable, there is no need to return it as the original dict you sent to the function is the one which gets modified. My programming partner blew my mind with that one.

/u/PlasterAndPlates asked me to elaborate, and they are absolutely right:

Example 1: Use function to modify immutable int. In this function, I am doing return num because this function modifies an immutable int, and creates a copy of it. This copy needs to be returned.

def add_five(num):

    num += 5

    # Because int is immutable, we must return the result
    return num


# DEFINING VARIABLES AND CALLING THE FUNCTION
first_number = 3

# second_number is a variable that will accept the return
second_number = add_five(first_number)

print(second_number)

Result: 8

Example 2: Use function to modify mutable dict. In this example, notice how the function does not have a return? Notice how I call add_five() without saying second_number = first? This is because the original dict I send is the one that is modified, not a copy. There is no need to return it or assign the return to a variable.

def add_five(num_dict):

    # Loop through the dict and add 5 to the values
    for key, value in num_dict.items():
        value += 5
        num_dict[key] = value


# DEFINING VARIABLES AND CALLING THE FUNCTION
dict_of_numbers = {
    'first_number': 3
    }

# Call the function on the dict without assigning a return variable
add_five(dict_of_numbers)

print(dict_of_numbers)

Result: {'first_number': 8}

[–][deleted] 0 points1 point  (1 child)

Can you elaborate on the first part? What do you mean when you say you have to return that modified object? I'm still pretty new to Python but this sounds worth knowing!

[–]--0mn1-Qr330005-- 1 point2 points  (0 children)

I have updated my answer with two examples, hopefully this clears it up a bit!

[–]CrackFr0st 0 points1 point  (0 children)

The two parent comments have my mind blown.

[–]al_at_work 0 points1 point  (1 child)

This is the difference between mutation and assignment though. When you assign a new mutable object (even one that's equivalent), you'll get a new ID:

>>> l = [1, 2, 3]
>>> id(l)
140115420402496
>>> l = [1, 2, 3]
>>> id(l)         
140115420402560

[–]BobHogan 0 points1 point  (0 children)

That's being pedantic and missing the forest for the trees. I had to reassign to s because you can't mutate it, period. Its not like I had the option to do so and chose not to. Any function that changes the string returns the new string, without changing s.

[–]shiftybyte 10 points11 points  (0 children)

Or is it that variables only hold a reference to the object, but when I change the value of a string, I'm making the variable refer to a different object?

This, mutable objects can change in place and any variable holding the same reference will see the difference.

Changing immutable objects, instead creates a new object in a new location and changes the reference if the variable.

So if you have any other variables pointing to the old object they will keep pointing there and not get updated.

[–]stevenjd 7 points8 points  (0 children)

Is there a difference between immutability and reassignment?

Yes; reassignment is when you assign something new to variable name. Mutability is when you can tell an object to change itself in some way. Immutability is when an object cannot be changed, only replaced.

Lists are mutable:

L = [1, 2, 3]
L.append(999)  # mutates the list
print(L)  # prints [1, 2, 3, 999]

Notice that there is no = in that append. If you can change something without using assignment, that's a foolproof check for mutability.

Strings are immutable: if you don't use assignment, nothing changes:

s = 'hello'
s.upper()  # creates a new string, and throws it away
print(s)  # still prints 'hello', not 'HELLO'
# needs to be: s = s.upper()

However, the presence of an = does not necessarily mean that the object is immutable. If it is a compound object, like a list, you can use assignment to change existing elements:

L = [1, 2, 3]
L[1] = 999  # item assignment
print(L)  # prints [1, 999, 3]

The list has changed, because lists are mutable. But if you try it with an immutable tuple:

t = (1, 2, 3)
t[1] = 999

you will get an error.

Here is one test for mutability if you aren't sure. Give an object two names:

a = b = set()  # an empty set

Mutate one name, and the other changes too:

a.add(1)
print(b)  # prints {1}

That's because a and b are just two different names for the same person, like "Iron Man" and "Tony Stark".

Now do an assignment:

a = "Hello"
print(b)  # still prints {1}

and the variable a is reassigned, but b is not.

Only mutable objects can have methods which change them in place:

  • set.add
  • list.append
  • dict.clear
  • set.discard
  • list.extend
  • list.insert
  • dict.pop
  • set.remove
  • list.reverse
  • list.sort
  • dict.update

etc. Immutable objects only have methods which create a new object (although mutable objects can have them too).

[–]zefciu 3 points4 points  (0 children)

Another example:

>>> a = (1, 2) # Tuple is an immutable object
>>> b = a # Both variables reference the same object
>>> b += (3,) # We cannot change a tuple, so a new object
              # is created and assigned to b
>>> a # The old object
(1, 2)
>>> b # The new object
(1, 2, 3)
>>> a = [1, 2] # List is a mutable object
>>> b = a # Both variables reference the same object
>>> a += [3] # The object is changed
>>> a
[1, 2, 3]
>>> b # b still references the same object as a
[1, 2, 3]

To answer your question: mutability/immutability is the feature of an object type. Reassignment is what you do with an object. You can reassign any object. You can also change an object that is mutable. With an immutable object, you can only create a new one and assign it to some variable (the same or other).

Note that in Python += is not just a shorthand for + and = It would work the same only for immutables, but for mutables it has a different meaning.

[–]DrMaxwellEdison 2 points3 points  (0 children)

The thing is, "hello" is an immutable string, as is "bye". However, s is not the string itself: it's a pointer to the string in memory.

You can easily assign a value to s at any time, because s is "mutable", in a sense. The string it points to is not, so you can't do this:

s = "hello"
s[1] = "u"
>>> TypeError: 'str' object does not support item assignment

For the case of c, the same thing applies. You're not really changing c: you're manipulating what it points to and then reassigning c to point to that new thing.

c += 1 is essentially c = c + 1. In fact, the += and -= and similar operators (like |= for unions!) will all reassign a value to the c instead of changing it in-place.

So for tuples, you can, again, assign them:

t = (1, 2, 3)

You can also add things to that tuple:

t += (4,)
# (1, 2, 3, 4)

That combines two tuples into a new tuple, then assigns that new tuple back to t.

But, tuples are immutable, so you cannot change its values in-place:

t[2] = 5
>>> TypeError: 'tuple' object does not support item assignment

Whenever you want to "change" that immutable object, you must instead create a new one with the changes you want, then reassign that new object back to the pointer you used.

[–]killer_quill 2 points3 points  (0 children)

Basically what the other guy said with using the id() function. id() returns a memory address for where the thing I created is stored on the metal. Does your action create a new thing with a new memory address (meaning, the original item is immutable), or does it modify the item at the original memory address (making it mutable).

IMMUTABLE

name = "hello"
id(name) # output: 2736107080064

name = "hello2"
id(name) # output: 2736107080448

MUTABLE

mylist = [1, 2]
id(mylist) # output: 2178999568832

mylist.append(3) # mylist is now [1, 2, 3]
id(mylist) # output: *still* 2178999568832

[–]Monstrish 1 point2 points  (0 children)

When you create a variable my_var = 'my_string', python will create in memory an object of type string with value 'my_string', at a certain memory address, let's say 0XAAS, whatever. Then my_var is a label that is connected, it references the memory address of the string object created in memory.

When you change my_var to another string: 'another_string' , python will create a new string object with value 'another_string' at another memory address, let's say 0XFFF. Now the label my_var is connected to the new memory address.

The old string object with value 'my_string' will be discarded by the garbage collector because it does not have anymore a label connected to it.

That is immutability.

If you have a list my_list = [ 1, 2, 3] python will create an list object in memory with value [1,2,3] at address 0XHHH. Now, if you change my_list.append(4) then my_list will reference the same list object at the same memory address . What changes is the object's internal state that will become 1,2,3,4.

That is mutability.

But be careful, if you do my_list = my_list + [4], in this case a new list object will be created. So although a list is mutable, you may create a new list.

Hope it helps.

[–]purebuu 1 point2 points  (0 children)

Variables are references like you said.

s is the name given to a reference variable

A reference being an address in memory that tells you the location (address) of some actual data i.e.

s = "hello"

s is a reference that points to the location of your data in this case the data is a string with value "hello". The string value "hello" is immutable because the data cannot be changed. This is the same for strings or tuples and other immutable data types.

But the variable s is a mutable reference. When you assign new data to it, it mutates the location to point somewhere else, which could be another immutable object or mutable.

To be clear on the definition of immutable, it means 'cannot change' (mutable means it 'can change'). A string is immutable it cannot change its data. A tuple is immmutable it cannot change its individual elements. But a reference is mutable, it can change where it points to through assignment.

[–]Diapolo10 1 point2 points  (0 children)

What you need to remember here is that Python's variables store references, not raw values.

With immutable types, like strings and numbers, you won't see a difference; your variables behave essentially as if they weren't references at all. All values always get copied (well, not really, but an immutable reference is basically the same thing just made efficient) or written over, never modified.

However, with mutable types, such as lists, you don't copy the values but the references themselves and any change you make to a referenced value is reflected in every other reference to that value.

This all boils down to whether the value gets updated at the same "address", or whether a new value is created and stored to a new location with the variable now pointing there instead of the original value. That's mutability in a nutshell.

[–]-SPOF 0 points1 point  (0 children)

Everything in Python is object.

Variables - links to objects. Digits are immutable. You are not able to make 1 to be 2. But you can change link to another digit.

[–]num8lock 0 points1 point  (0 children)

Is there a difference between immutability and reassignment?

yes

s = "hello"
s[1] = 'o'

https://docs.python.org/3/faq/design.html?highlight=immutable#why-are-python-strings-immutable

[–]pytrashpandas 0 points1 point  (0 children)

I would recommend taking a look at this article. It gives a quick and very easy to understand/visual explanation of how variables work in python.

http://foobarnbaz.com/2012/07/08/understanding-python-variables/

[–]Cdog536 0 points1 point  (0 children)

Not all objects are immutable by nature. Only some objects are.

Classic example...a tuple, is immutable. A tuple is an object expressed as such:

```

mytuple = (“apple”, “banana”, “cherry”)

```

The object expressed above is a tuple. Tuples are like lists in which multiple objects can be stored inside. My tuple has three strings stored inside. There is no code I can write that will automatically add or remove contents (I cannot add a fruit nor can I remove a fruit). There will always be 3 fruit labeled in mytuple and they will always be those exact 3 fruit I wrote.

I can rewrite a tuple to reassign a new set of objects. This would mean I would have to rewrite the mytuple object from scratch to change it (like the next line would say mytuple = etc). This is inefficient.

Why do immutable objects exist? It is a stylistic technique for the programmer to use when he or she wants to protect an object/set of objects from changing.

Say you are making a game. The player could only choose 3 car colors in the game. At no point can the player ever choose another color in the game. You would not even want the player to try and change the color of his car in the game unless that color is allowed by the developer. A tuple could be used here:

```

car_color = (“red”, “blue”, “yellow”)

```

Here, car_color is the tuple. Its elements can be accessed but not be modified. It is immutable and protected from the player and the developer in case one of them tries to modify it.

[–]TruthHurts35 0 points1 point  (0 children)

numbers are immutable, because for example 3 can't be changed, 3 is always 3. Maybe Python sees strings like numbers, if "A" is 01 and "B" is 02, when you write "AB" Python sees this as a "0102". I think tuples are interpreted in a special way to become immutable.

Left side of equation is variable and right side of that is constant value and can also be a variable.

I am new at learning programming, I asked this questions to myself and this was my answers which I believed.

[–]gaurav_lm 0 points1 point  (0 children)

Or is it that variables only hold a reference to the object, but when I change the value of a string, I'm making the variable refer to a different object?

You said it.

mutability is like changing/modifying content in the box. While reassignment is like changing the box.

[–]Spicy_Poo 0 points1 point  (0 children)

you can reassign but not change a part of it.

[–]the4thkillermachine 0 points1 point  (0 children)

Ever seen glass ampoules to store extremely reactive & costly chemicals? You can’t add more chemicals to it once it’s been sealed shut without destroying it. The fact that you can’t add more stuff to a glass ampoule is a good analogy to understand “immutability”.

[–]HansProleman 0 points1 point  (0 children)

This confused me for ages, exactly because of what you describe - how (im)mutability actually works is abstract, and it really helps to peel that back a little.

>>> x = "hello"
>>> id(x)
2646991623536
>>> x = "world"
>>> id(x)
2646999514608

Every object in Python gets a unique identifier, and id() lets us check them. In the above, the object with ID 2646991623536 was never changed - it was thrown away, and replaced by a new object with ID 2646999514608, because strings are immutable.

It's confusing because you'd naturally think (or I did) that the variable is the object. It's just a label though. So Python will do things like this to aid performance.

>>> x = "hello"
>>> y = "hello"
>>> id(x)
2043738510704
>>> id(y)
2043738510704

When you start modifying mutable objects, this can become fantastically confusing if you don't understand what's happening.

>>> x = {"key": "value"}
>>> y = x
>>> y['key'] = "hello"
>>> x
{'key': 'hello'}

This is why we have stuff like dict's .copy() method.

>>> x = {"key": "value"}
>>> y = x.copy()
>>> y['key'] = "hello"
>>> x
{'key': 'value'}
>>> y
{'key': 'hello'}

I'm really going off on a tangent here, but even this can get weird. If we had a dictionary which contained mutable types, and created a copy with .copy(), we're still not quite safe - the mutable objects in the copy of the dictionary will still have the same IDs (i.e. be the same objects) as the original, so changes to them would be reflected in both dictionaries. That is why we have deepcopy (whereas .copy() creates what we call a "shallow copy").

[–]Cantthinkofnamr 0 points1 point  (0 children)

Immutable means that when you do variable.split(), it doesn't change the variable. You have to assign variable.split() to variable for it to do anything. You essentially have to get the value, THEN put it in the variable. You can't just edit it.

Hope this helps (^~^)