FAQ: Python Namespaces - Local Namespace

This community-built FAQ covers the "Local Namespace " exercise from the lesson “Python Namespaces”.

Paths and Courses
This exercise can be found in the following Codecademy content:

Learn Intermediate Python 3

FAQs on the exercise _Local Namespace _

There are currently no frequently asked questions associated with this exercise – that’s where you come in! You can contribute to this section by offering your own questions, answers, or clarifications on this exercise. Ask or answer a question by clicking reply (reply) below.

If you’ve had an “aha” moment about the concepts, formatting, syntax, or anything else with this exercise, consider sharing those insights! Teaching others and answering their questions is one of the best ways to learn and stay sharp.

Join the Discussion. Help a fellow learner on their journey.

Ask or answer a question about this exercise by clicking reply (reply) below!
You can also find further discussion and get answers to your questions over in #get-help.

Agree with a comment or answer? Like (like) to up-vote the contribution!

Need broader help or resources? Head to #get-help and #community:tips-and-resources. If you are wanting feedback or inspiration for a project, check out #project.

Looking for motivation to keep learning? Join our wider discussions in #community

Learn more about how to use this guide.

Found a bug? Report it online, or post in #community:Codecademy-Bug-Reporting

Have a question about your account or billing? Reach out to our customer support team!

None of the above? Find out where to ask other questions here!

Hi everyone! I actually do have a question. Talking of the inner workings - say I have the two functions

def divide(num1, num2):
  result = num1 / num2
  print(locals())

def multiply(num1, num2):
  product = num1 * num2

with their according local namespaces:

{'result': 0.75, 'num2': 4, 'num1': 3}

{'product': 200, 'num2': 50, 'num1': 4}

How come the order of the dictionary items is just the opposite of what I would have expected?

3 Likes

I agree with [nikkibeach4160629933] the order of printing the dictionary is not usual, would be great to get at least a few words about it. Why the “print locals” function starts from the end of local scope, not from the begining? Thank you in advance, a stranger that decided to answer!!

2 Likes

I’m also interested, although ordering of items of Python dict may not make much sense.

I’m not completely sure, but I guess that, internally, names and values of local variables are stored in something like stack, LIFO=Last-In-First-Out.

Another interesting thing is that locals() seems to update the dictionary rather than just read. I tried to call locals() once before creating the third variable:

def divide(num1, num2):
  print(locals())
  result = num1 / num2
  print(locals())

divide(3, 4)

Then I got

{'num2': 4, 'num1': 3}
{'num2': 4, 'num1': 3, 'result': 0.75}
2 Likes

Thank you, it’s getting more clear, I think I just need to get used to all this. The more the better.

2 Likes

So far as I’m aware in CPython at least, locals() should only ever be considered a representation of the actual local variables for a function (locals are not stored in a dictionary for a function). As the documentation notes-

Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

This is unlike globals where you can modify global level variables using the globals() dictionary (although it’s probably the wrong way of working most of the time). As you mention I’d not rely on the order of the dictionary anyway.

For CPython the names themselves are created and included into the function object when the function is first defined (compiled) and executing the function merely fills them up and runs the function instructions (if you want more info look into execution frames, this is what allows for multiple calls to the same function and nested calls with entirely different values).

def func(a): b = 3 c = a + b del b print(b) return c print(func.__code__.co_varnames) # defaults and constants are also bound # into this function object and assigned # to the given names when executed func(0) # to show unboundlocal

This is why you may sometimes see the UnboundLocalError. It’s a local name that hasn’t yet been bound to an object (in that particular execution frame). As in the above example you can force this error by deliberately removing the binding. The name arguably still exists, it’s just not bound to anything.

2 Likes

I verified what you pointed out about modifying locals() dictionary.

def func(): a = 1 print(a) locals()['a'] = 2 print(a) func() b = 1 print(b) locals()['b'] = 2 print(b)

It fails to attempt to modify the local namespace in the func. On the other hand, locals() called outside func actually accesses the global namespace and (illegally?) modifies it.

I found the following article, modifying the locals() dictionary (but not the true local namespace).

def foo(cond): if cond: z = 1 return z else: loc = locals() x = 1 locals()['y'] = 2 exec("z = x + y") print(loc) print(locals()) foo(False) print(foo.__code__.co_varnames)

In this example it modifies the locals() dictionary but not the true local namespace. z exists in co_varnames but is not bound to anything in the sense of true local namespace and is removed from the dictionary when the final locals() is called. If we add a line such as print(z) just below exec, it will raise UnboundLocalError.

y does not exist in co_varnames but is added into the dictionary in an unusual way. If we add a line such as print(y) in foo, it will raise NameError since even the name of y does not exist in the true local namespace.

Aye that all fits the model I’m aware of. There is no way to dynamically modify the names in a function, by exec or otherwise, the objects those names refer to can of course be changed but that’s the same for all scopes.

I think the link mentions it but there is effectively a set size array for locals, a symbol table or at least close to one. In bytecode terms whenever you use a name it loads the object (e.g. the object referred to by local #3) to the stack and continues running instructions. Loading of local names is comparatively fast compared to outer scope names (LOAD_FAST bytecode) so the whole thing is a bit of an optimisation trick (a similar method is used for pre-defined constants e.g. load constant number 10).

There is no route to dynamically alter the number or the names of those symbols, only what they point to. In that way the locals namespace is very different to say the module level namespace or the namespace associated with a typical custom class or instances of the same (at present CPython uses dictionaries for such namespaces but that’s an implementation detail).

In short, locals() is a representation of a function local namespace.

2 Likes