Mutable and Immutable Variables and Namespaces in Python
Namespace is a place where a variable is stored. There are four namespaces in Python as local, nested (or enclosing), global and built-in.
What type of programming language is Python?
Is it an interpreted or compiled language? there’s an assumption that Python is an interpreted language meaning an interpreter (like CPython which is the dominant interpreter in the ecosystem) runs the code line by line.
It’s versus compiled language which a compiler compiles the entire program into machine code. I don’t want to get into comparing them.
I said there’s an assumption that Python is an inerpreted language which is not totally correct!
The way Python executes a program is that the interpreter compiles the source code into bytecode which is cached in pyc
file (so next time it’s not needed to translate it into bytecode and the program runs faster). afterwards the Python Virtual Machine executes the bytecode. note that this bytecode is only understandable for the Python VM, not the machine.
With that being said how can see what the bytecode looks like. dis
module can help us here.
|
|
Output
121 0 LOAD_CONST 1 (1)
2 STORE_FAST 0 (c)
4 LOAD_CONST 0 (None)
6 RETURN_VALUE
Let’s get into Namespaces.
Local
Defining a function creates a local namespace. the variables that defined in the function are only accessible within it and not outside.
|
|
c
is defined in the function and is only accessible in that namespace.
If you try to access c
outside the function you’ll get a NameError
error. the following code throws that error.
|
|
Output
|
|
Consider the following code
|
|
The bytecode of the main
function
98 0 LOAD_GLOBAL 0 (print)
2 LOAD_GLOBAL 1 (c)
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
LOAD_GLOBAL loads the variable from the global namespace.
LOAD_GLOBAL(namei)
Loads the global named co_names[namei>>1] onto the stack.
So the interpreter looks for that variable in the global namespace.
To see what names are available in the local namespace you can check output of locals()
and globals()
for global namespace.
|
|
Let’s modify the variable
|
|
Output
[1, 2]
The same flow happens when a variable is modified.
|
|
Before continuing the article I’d like talk about mutable and immutable variables in Python.
Mutable and Immutable variables
Mutable variables are mutable and can be modified however immutable variables can not be modified. if you modify them, a new variable will be created.
Mutable objects can change their value but keep their id(). #
An object with a fixed value. Immutable objects include numbers, strings and tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key in a dictionary. #
Mutable types include dictionary, list, set and user-defined classes
Immutable types include numbers, strings and tuples
id()
returns the memory address of an object (In CPython). take the following code
|
|
If you run the code, you’ll see different values because changing an immutable objects, produces a new object. but for mutable objects it’s not the case
|
|
This time the values are same because changing the object didn’t produce a new object.
In Python we have the concept of assignment. unlike other languages, this statement c = 1
is read by Python as point variable c to the memory address that holds value 1
|
|
You’ll get same output. let’s see another example
|
|
We can read b = a
as point variable b to the memory address that variable a points to
. so b
points to THE memory address that a
points to. so now b
points to that address and it doesn’t have any business with a
anymore. in the next line a = 1
, a
points to another memory address.
Output
898989
Another one
|
|
Output
[1, 2]
The same rules apply when a function changes its arguments. if the functions changes an argument that is mutable, the memory gets changed so outside the function, it has the change. if it’s immutable, changing it inside the function, doesn’t change the value that memory address holds,
|
|
Output
[1]
UnboundLocalError
Let’s use another type for the variable and modify it
|
|
Output
UnboundLocalError: local variable 'c' referenced before assignment
What happened? let’s check the bytecode
98 0 LOAD_FAST 0 (c)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (c)
99 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (c)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
This time it’s LOAD_FAST which loads variable from the local namespace.
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
When the varible is assigned inside the function by c = c + 1
, the Python VM reads the right side of the assignment operator first, it load c
, add it to 1
and store it in the local variable c
. so c
is a local variable but it’s not defined in the local scope, and when line is getting executed there’s no value for c
, there you get the error!
In the previous code, the variable type was a list, we used append
to modify it. the other way would be c = c + [1]
this code procudes the same result, because we know that this expression runs the __iadd__
magic method of the list object and this is not assignment. however for example this code c = c + 1
a new address in the memory is used for the variable and it’s not the old one.
To fix it we need to instruct the interpreter to load the variable from the global scope.
|
|
Let’s look at the byecode
99 0 LOAD_GLOBAL 0 (c)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_GLOBAL 0 (c)
100 8 LOAD_GLOBAL 1 (print)
10 LOAD_GLOBAL 0 (c)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
It’s more like it!
Nested
Nested namespace is available for nested functions. the local namespace of the outer
function is the nested namespace for the inner
function.
|
|
The same issue that we had for assigning we have here but to fix it we need to use nonlocal
keyword
|
|
Global
The scope outside functions is global. you can see what variables are defined by looking at the globals()
output.
Built-in
It includes the built-in functions exceptions and objects. check globals()['__builtins__']
or dir(__builtins__)