This was great - I definitely predicted the title wrong haha. I also recently learned that simultaneous assignment is not perfectly simultaneous!! Keep it up Stephen
I learned a lot with this one. When you started with talking about being able to change the values in a dictionary in a tuple, my first guess for why was *close*. I figured that the values of the tuples were the dictionary, so as long as the dictionary is still there, the tuple hasn't changed, no matter if changes are made inside the dictionary. But it makes sense that it's not the actual object in the tuple but a reference to it.
But I have a question about reassigning the variable name of a string (or any other immutable type). When you assign the new string to the old string's variable name, what happens to the old string? Is it gone, removed from memory or just hanging out, unreachable until garbage collection?
Your guess was spot on! The tuple and the dictionaries are stored in different memory locations — different shelves in the storage room, if you prefer. So one structure contains the reference for where to find "its contents", which are stored elsewhere in the storage room
But I'll summarise here: each object created has a reference count, so it stores within it the number of references there are to it in the program. So if your create a list and assign to a name:
`my_numbers = [3, 5, 6]
the object created, of type list, will contain the values of course, but also a reference count of 1. There's only the name `my_numbers` referring to it.
But then, you write `lucky_numbers = my_numbers` and the same object now has two references, so ref count becomes 2.
You may then add `important_stuff = {"top priority": my_numbers}` - even though you didn't assign a new name to the list, you get refer to it using `important_stuff["top priority"]`, so ref count becomes 3, and so on.
You can also remove references by removing the list from the dictionary above, for example, reassigning a name to another object (as in the original example you asked about), or using the `del` keyword which deletes a name (not the object)
When an object's reference count reaches 0, you no longer have a way of accessing that object, therefore the garbage collector will take care of it soon.
I just keep learning stuff today. So if del deletes the keyword, not the object, calling del is kind of superfluous, unless the point is just to make sure that nothing can access that value before the function ends
Fun fact: At least in CPython, strings are technically not immutable (although for all practical purposes they are). This is due to an optimization made for string addition.
If you do a:
string = string + stringtwo
and the reference count of string before the operation is one, then CPython reuses string instead of allocating a new memory location. You can check this by doing id(string) before and after the operation.
Even when CPython does find cases when it reuses the same memory location, it's still creating a new object rather than mutating the old one, I'd say. But using the same memory address. It's all academic as this won't make any difference in practice, but I'd most definitely classify strings as an immutable type!
Yes, to clarify, whether it decides to create a new object or reuse the existing object depends on several factors. However, we can see this if we iteratively add:
> Even when CPython does find cases when it reuses the same memory location, it's still creating a new object rather than mutating the old one.. It's all academic as this won't make any difference in practice, but I'd most definitely classify strings as an immutable type!
In practice, since it only does this when the ref count is one, it's the same as strings being immutable. I just find this corner case very fascinating.
This was great - I definitely predicted the title wrong haha. I also recently learned that simultaneous assignment is not perfectly simultaneous!! Keep it up Stephen
Haha! The next one is easier to guess (I think) from the title: "Telling The Truthy"
You know what Stephen, I think you'll still surprise me. Maybe something to do with True/False and Boolean logic?!
I learned a lot with this one. When you started with talking about being able to change the values in a dictionary in a tuple, my first guess for why was *close*. I figured that the values of the tuples were the dictionary, so as long as the dictionary is still there, the tuple hasn't changed, no matter if changes are made inside the dictionary. But it makes sense that it's not the actual object in the tuple but a reference to it.
But I have a question about reassigning the variable name of a string (or any other immutable type). When you assign the new string to the old string's variable name, what happens to the old string? Is it gone, removed from memory or just hanging out, unreachable until garbage collection?
Your guess was spot on! The tuple and the dictionaries are stored in different memory locations — different shelves in the storage room, if you prefer. So one structure contains the reference for where to find "its contents", which are stored elsewhere in the storage room
Your second question is a topic I'm planning to write about very soon. Are you on Twitter / X, if you are I had this thread there a long while ago: https://x.com/s_gruppetta_ct/status/1606257500159766529?s=20
But I'll summarise here: each object created has a reference count, so it stores within it the number of references there are to it in the program. So if your create a list and assign to a name:
`my_numbers = [3, 5, 6]
the object created, of type list, will contain the values of course, but also a reference count of 1. There's only the name `my_numbers` referring to it.
But then, you write `lucky_numbers = my_numbers` and the same object now has two references, so ref count becomes 2.
You may then add `important_stuff = {"top priority": my_numbers}` - even though you didn't assign a new name to the list, you get refer to it using `important_stuff["top priority"]`, so ref count becomes 3, and so on.
You can also remove references by removing the list from the dictionary above, for example, reassigning a name to another object (as in the original example you asked about), or using the `del` keyword which deletes a name (not the object)
When an object's reference count reaches 0, you no longer have a way of accessing that object, therefore the garbage collector will take care of it soon.
I just keep learning stuff today. So if del deletes the keyword, not the object, calling del is kind of superfluous, unless the point is just to make sure that nothing can access that value before the function ends
If the name you’re deleting is the last remaining reference to the object, then the object will be deleted from memory too
Fun fact: At least in CPython, strings are technically not immutable (although for all practical purposes they are). This is due to an optimization made for string addition.
If you do a:
string = string + stringtwo
and the reference count of string before the operation is one, then CPython reuses string instead of allocating a new memory location. You can check this by doing id(string) before and after the operation.
But:
```
name = "hello"
print(id(name))
# 4378810272
name = name + "bye"
print(id(name))
# 4379196848
```
This is 3.12
Even when CPython does find cases when it reuses the same memory location, it's still creating a new object rather than mutating the old one, I'd say. But using the same memory address. It's all academic as this won't make any difference in practice, but I'd most definitely classify strings as an immutable type!
Yes, to clarify, whether it decides to create a new object or reuse the existing object depends on several factors. However, we can see this if we iteratively add:
>>> import collections
>>> letters = ['h', 'e','l', 'l','o',' ', 'w','o','r','l','d']
>>> ids = []
>>> string = ''
>>> for letter in letters:
... string = string + letter
... ids.append(id(string))
...
>>> print(collections.Counter(ids))
Counter({1601851586912: 3, 1601851587056: 3, 1601850291696: 2, 140723900781096: 1, 1601850286128: 1, 1601850284272: 1})
> Even when CPython does find cases when it reuses the same memory location, it's still creating a new object rather than mutating the old one.. It's all academic as this won't make any difference in practice, but I'd most definitely classify strings as an immutable type!
Not exactly. It's indeed mutating the old object. https://github.com/python/cpython/blob/3.12/Objects/unicodeobject.c#L10943-L10948
In practice, since it only does this when the ref count is one, it's the same as strings being immutable. I just find this corner case very fascinating.