So then how is it working? What happens when we do something like this - myvar = 5?

We, the programmers, grew up learning that the concept of variables is very important one. When asked by somebody what is a variable and what happens when we create one, assign it a value, reassign the value, delete a variable etc., we used to say this:

On the line of code of creating and assigning values to variables like this

myvar = 5

the compiler reserves the memory based on the type of the variable and store the value in that memory location. On reassigning, the value in that memory location is replaced with reassigned value and on deleting, memory is cleared and released to OS. This is pretty basic to every programmer.

In python, it is a little different, the definition of variables is very similar to definition of variables in Algebra that is simply "value of a variable varies".

Python interpreter does not implement the concept of variable the way traditional compilers handle i.e. reserving memory, issuing type specific instructions, replace the value in that reserved memory location when the variables are reassigned with differnt values etc.

So let us use better terminology "names" and "objects" when refer to variables and its values in Python. We can roughly compare names to variables and objects values of the variables.

In run-time, everything is an object. We can create an object (value) without assigning it to any name (variable). What happens when we type just a number as a line of code and send it to the Python interpreter? Let us see.

In [1]:
1
Out[1]:
1

Here above, we just created an object (value) but we did not assign it to any variable. This proves that object (value) creation is not related to name. Objects can be assigned to variables after they are created so objects do not necessarily have to know their assigned names. However, a name has to know what object is assigned to it.

Each object has a single unique id which is a memory addrss in CPython. See the id below.

In [3]:
id(1)
Out[3]:
4413625472
In [5]:
a
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-3f786850e387> in <module>
----> 1 a

NameError: name 'a' is not defined

Above, the interpretor assumes that a is a name and complains that it is not defined.

In [6]:
a = 1

Now name a is accepted since it is assigned to an object and so defined.

In [7]:
"a"
Out[7]:
'a'

And above, there is no complaint because the literal "a" is an object and is created, though it does not have to be assigned to any names. So names know what object is assigned to them but objects do not necessarily have to know their assigned names. More of id.

In [8]:
id("a")
Out[8]:
140423471131824

Above is the unique id of the literal "a" object.

Objects have attributes. Let us see explore the objects.

In [9]:
"a".upper
Out[9]:
<function str.upper()>
In [10]:
"a".upper()
Out[10]:
'A'

Now we got new string object returned. Can we get the all attributes of this object?

In [11]:
dir("A")
Out[11]:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isascii',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Every object has single type. We get the type by this.

In [12]:
type("A")
Out[12]:
str

Since the type of an object is its class, we can get the type of the object this way too.

In [14]:
"A".__class__
Out[14]:
str

Reference: Stuart Williams - Python Epiphanies - PyCon 2018 https://www.youtube.com/watch?v=-kqZtZj4Ky0&t=498s