It is assumed that you already know how to write simple Python programs and understand the basic syntax (if, for, functions, lists). Here we are not learning how to use the language, but why it works the way it does.

Consider this code:

x = 10

def show():
x = 20
print(x)

show()

print(x)

What will it print?

20
10

But wait.

How is this even possible?

There is a variable x in the program.

First, we assigned 10 to it, and then inside the function we assigned 20 to it.

If x is a name associated with an object, why does it point to 10 again after the function call?

Did Python somehow restore the old value?

No.

Nothing was restored.

In fact, this program contains two different names called x.


In the previous article, we learned about namespaces.

A name does not exist on its own.

It exists inside some namespace and is associated with an object there.

This means that identical names can exist at the same time if they belong to different namespaces.

That is exactly what happens in our example.

Before the function is called, there is a namespace containing:

x ───► 10

When Python starts executing the show() function, it gets its own namespace.

Inside it, this statement is executed:

x = 20

Now there is another binding:

x ───► 20

So the situation looks roughly like this:

Outer namespace:

x ───► 10


Function namespace:

x ───► 20

The names have the same spelling.

But they are different entries.


That’s why, while the function is running:

print(x)

Python finds the x associated with the object 20.

After the function finishes, its namespace is no longer the current namespace.

We return to the outer namespace.

There, x is still associated with 10.

So:

print(x)

prints:

10

Nothing was changed and then restored.

There were simply two different names called x all along.


This is a very important point.

When we say:

“Inside the function, the variable x has the value 20.”

That is a convenient simplification.

More precisely, we should say:

“Inside the function’s namespace, the name x is associated with the object 20.”

At the same time, in the outer namespace, the name x is associated with a different object:

x ───► 10

That’s why these two x names do not interfere with each other.


Let’s modify the program slightly.

x = 10

def show():
print(x)

show()

This time, we don’t create a local x inside the function.

But print(x) still works.

Python prints:

10

Where did the function get this name?

There is no x in its own namespace.

So Python must look somewhere else.

And this brings us to one of the most important rules in Python.

If a name is not found in the current namespace, Python can continue searching in another one.


Now let’s make the situation a little more interesting.

x = 10

def show():
x = 20
print(x)

show()

print(x)

Here Python finds x inside the function and stops searching.

Now consider this:

x = 10

def show():
print(x)

show()

There is no x inside the function.

So Python continues searching in the outer namespace.

This means that a function does not simply “know” all the variables in the program.

It looks up names according to specific rules.


This also explains another fairly common error.

Consider:

x = 10

def show():
print(x)
x = 20

show()

At first glance, it might seem that the program should print 10 and then assign 20.

But instead, it will terminate with an error.

The reason is that Python treats x inside the function as a local name.

The statement:

x = 20

creates a binding for the name x in the function’s namespace.

Therefore, print(x) tries to access that local x.

But at this point, it has not been associated with an object yet.

So Python raises:

UnboundLocalError

This can seem strange as long as we think of variables as “boxes”.

But if we look at everything through namespaces, the behavior becomes much easier to understand.

Python does not simply say:

“I see a global variable x, so I’ll use it.”

First, it determines which name x is meant at this particular point in the program.


And this leads to a natural question.

If Python can search through several namespaces, in what order does it do so?

Why does it sometimes use a local name?

Why does it then look in an outer namespace?

What happens with global variables?

And what does the mysterious sequence of letters LEGB, which is often presented as something you simply have to memorize, actually mean?

In the next article, we’ll explore this rule without memorization — we’ll simply follow how Python looks up a name.