11 Comments

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

Expand full comment
author

Haha! The next one is easier to guess (I think) from the title: "Telling The Truthy"

Expand full comment

You know what Stephen, I think you'll still surprise me. Maybe something to do with True/False and Boolean logic?!

Expand full comment
Jan 19Liked by Stephen Gruppetta

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?

Expand full comment
author

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.

Expand full comment

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

Expand full comment
author

If the name you’re deleting is the last remaining reference to the object, then the object will be deleted from memory too

Expand full comment

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.

Expand full comment
author

But:

```

name = "hello"

print(id(name))

# 4378810272

name = name + "bye"

print(id(name))

# 4379196848

```

This is 3.12

Expand full comment
author

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!

Expand full comment
Feb 7Liked by Stephen Gruppetta

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.

Expand full comment