Making integers mutable would be very counter-intuitive to the way we are used to working with them.
Consider this code fragment:
a = 1 # assign 1 to a
b = a+2 # assign 3 to b, leave a at 1
After these assignments are executed we expect a to have the value 1 and b to have the value 3. The addition operation is creating a new integer value from the integer stored in a and an instance of the integer 2. If the addition operation just took the integer at a and just mutated it then both a and b would have the value 3.
So we expect arithmetic operations to create new values for their results - not to mutate their input parameters.
However, there are cases where mutating a data structure is more convenient and more efficient. Let's suppose for the moment that list.append(x) did not modify list but returned a new copy of list with x appended.
Then a function like this:
def foo():
nums = []
for x in range(0,10):
nums.append(x)
return nums
would just return the empty list. (Remember - here nums.append(x) doesn't alter nums - it returns a new list with x appended. But this new list isn't saved anywhere.)
We would have to write the foo routine like this:
def foo():
nums = []
for x in range(0,10):
nums = nums.append(x)
return nums
(This, in fact, is very similar to the situation with Python strings up until about 2.6 or perhaps 2.5.)
Moreover, every time we assign nums = nums.append(x) we would be copying a list that is increasing in size resulting in quadratic behavior.
For those reasons we make lists mutable objects.
A consequence to making lists mutable is that after these statements:
a = [1,2,3]
b = a
a.append(4)
the list b has changed to [1,2,3,4]. This is something that we live with even though it still trips us up now and then.
Making integers mutable would be very counter-intuitive to the way we are used to working with them.
Consider this code fragment:
a = 1 # assign 1 to a
b = a+2 # assign 3 to b, leave a at 1
After these assignments are executed we expect a to have the value 1 and b to have the value 3. The addition operation is creating a new integer value from the integer stored in a and an instance of the integer 2. If the addition operation just took the integer at a and just mutated it then both a and b would have the value 3.
So we expect arithmetic operations to create new values for their results - not to mutate their input parameters.
However, there are cases where mutating a data structure is more convenient and more efficient. Let's suppose for the moment that list.append(x) did not modify list but returned a new copy of list with x appended.
Then a function like this:
def foo():
nums = []
for x in range(0,10):
nums.append(x)
return nums
would just return the empty list. (Remember - here nums.append(x) doesn't alter nums - it returns a new list with x appended. But this new list isn't saved anywhere.)
We would have to write the foo routine like this:
def foo():
nums = []
for x in range(0,10):
nums = nums.append(x)
return nums
(This, in fact, is very similar to the situation with Python strings up until about 2.6 or perhaps 2.5.)
Moreover, every time we assign nums = nums.append(x) we would be copying a list that is increasing in size resulting in quadratic behavior.
For those reasons we make lists mutable objects.
A consequence to making lists mutable is that after these statements:
a = [1,2,3]
b = a
a.append(4)
the list b has changed to [1,2,3,4]. This is something that we live with even though it still trips us up now and then.
What are the design decisions to make numbers immutable in Python?
There are several reasons for immutability, let's see first what are the reasons for immutability?
1- Memory
- Saves memory. If it's well known that an object is immutable, it can be easily copied creating a new reference to the same object.
- Performance. Python can allocate space for an immutable object at creation time, and the storage requirements are fixed and unchanging.
2- Fast execution.
- It doesn't have to copy each part of the object, only a simple reference.
- Easy to be compared, comparing equality by reference is faster than comparing values.
3- Security:
- In Multi-threading apps Different threads can interact with data contained inside the immutable objects, without to worry about data consistency.
- The internal state of your program will be consistent even if you have exceptions.
- Classes should be immutable unless there's a very good reason to make them mutable....If a class cannot be made immutable, limit its mutability as much as possible
4- Ease to use
- Is easier to read, easier to maintain and less likely to fail in odd and unpredictable ways.
- Immutable objects are easier to test, due not only to their easy mockability, but also the code patterns they tend to enforce.
5- Keys must be immutable. Which means you can use strings, numbers or tuples as dictionary key. This is something that you want to use.
The hash table implementation of dictionaries uses a hash value calculated from the key value to find the key. If the key were a mutable object, its value could change, and thus its hash could also change. But since whoever changes the key object can’t tell that it was being used as a dictionary key, it can’t move the entry around in the dictionary. Then, when you try to look up the same object in the dictionary it won’t be found because its hash value is different. If you tried to look up the old value it wouldn’t be found either, because the value of the object found in that hash bin would be different.
Going back to the integers:
Security (3), Easy to use (4) and capacity of using numbers as keys in dictionaries (5) are reasons for taken the decision of making numbers immutable.
Has fixed memory requirements since creation time (1).
All in Python is an object, the numbers (like strings) are "elemental" objects. No amount of activity will change the value 8 to anything else, and no amount of activity will change the string “eight” to anything else. This is because a decision in the design too.
While I can understand that strings are immutable but not lists, not sure how integers and float immutable. Raised this query on a private Slack community, and received a reply that can be summarized:
In Python, every value is treated as an object, and each object is an instance of a class. When you assign a value to a variable, Python references the corresponding object in the heap memory. If the object doesn't exist, Python creates an instance of the appropriate class in the heap memory.
For example, when you write x = 17, Python references the object with the value 17 in the heap memory. If you later change the variable's value, such as x = 20, Python creates a new object of the Integer class with the value 20. The object with the value 17 is then marked for garbage collection and eventually removed from the heap memory.
This immutability of values in Python, particularly for integers, stems from the fact that when you assign a new value to a variable, Python creates a new instance of the corresponding class rather than modifying the existing object.
Contrast this with primitive data types in other languages. For instance, in a language using a 4-byte integer, if you write x = 17, the value 17 is stored in a 4-byte memory space. When you later change the value to x = 20, the new value is stored in the same memory space, with the same memory address. This behavior is characteristic of mutable data types, where the value of the variable can be directly modified in the allocated memory space without creating a new instance.
In summary, Python's approach to object references and immutability differs from languages with mutable primitive data types, where values can be modified in-place within the same memory space.
Another helpful comment rephrased as follow:
Strings in Python are immutable, meaning that attempts to mutate or change them are not allowed. For instance, if you have a string aStr = "abc", trying to modify a character with aStr[0] = "d" would result in an error. The immutability of strings ensures that the original value referenced by the variable remains unchanged, and any attempt to alter it directly is met with an error. In this case, print(aStr) would output "abc" because the value at the original memory address remains unaltered.
On the other hand, lists in Python are mutable, permitting mutation operations. For example, with a list aList = ["a", "b", "c"], you can successfully perform aList[0] = "d", and upon printing, the list would now be ["d", "b", "c"]. This is possible because lists allow modifications to the values at the same memory address without creating a new instance.
In contrast, integers and floats are immutable in Python. Attempting to demonstrate mutation for integers and floats is challenging, as most operations involving these types result in reassignment rather than mutation. Reassigning a new value to an integer or float variable doesn't modify the original value stored at a specific memory address but rather assigns a new value stored at another memory address. Consequently, the concept of mutation is not applicable to integers and floats in the same way it is for mutable data types like lists.
Any additional comment on the above will be helpful in understanding further.
UPDATE: Thanks for the useful comments.
Here is a problem:
Write an if statement that lets a user know which of these rewards he/she bagged based on the rewards they scored, which is stored in the integer variable rewards.
| Rewards | Prize |
|---|---|
| 1 - 40 | wooden furniture |
| 41 - 140 | no prize |
| 141 - 170 | world trip |
| 171 - 200 | peanut |
All of the lower and upper bounds here are inclusive, and rewards can only take on positive integer values up to 200.
In your if statement, assign the result variable to a string holding the appropriate message based on the value of rewards. If they've won a prize, the message should state "Congratulations! You won a [prize name]!" with the prize name. If there's no prize, the message should state "Oh dear, no prize this time."
Here is the successful code that I am trying to figure out:
rewards = 100
if rewards <= 40:
result = "Congratulations! You won a wooden furniture"
elif rewards <= 140:
result = "Oh no prize this time."
elif rewards <= 170:
result = "Congratulations! You won a world trip!"
else:
result = "Congratulations! You won a peanut!"
print(result)
Not sure if rewards is indeed an integer type and immutable, then how the value initialized and then changed. On the face value, it appears that there is already somewhere the rewards is recorded/stored. Then that stored rewards is checked against the variable rewards initialized to 100.
Why are some types made immutable in Python?
How integers and floats immutable
Videos
The integer is immutable.
When you write x=5, x points to a memory location that holds 5.
When you go on and code y=x, the variable y points to the same location as x.
Then you type x+1=6, and now x points to a new location that holds 6, and not the previous location. ( Here, the integer still holds immutable because the original integer 5 still exists, but the variable x is not bound to it now. x is now bound to a new location. But y is still bound to the integer 5)
But y still points to the same location that holds 5.
So, integers are still immutable and this is how it works. To see it better, use id(x) or id(y) after every step.
x and y are both references.
x = 5 : x is a reference to 5
y = x : y is also a reference to 5
x += 1 : x is now a reference to 6, y is still a reference to 5
Hey Reddit,
I've been working with Python and noticed that some data types are immutable, like integers, floats, strings, and tuples. While I understand the concept of immutability, I'm curious about the reasoning behind making certain types immutable.
I was surprised to learn that there is this difference without any syntax that indicates immutability. My impression is that most objects are mutable with a seemingly random selection of types being immutable.
Why did the creators of Python decide to make these types immutable? What are the benefits of this design choice?
I'd love to hear your thoughts and experiences on this topic!
Thanks in advance!