Namespaces and Scope in Python

  • By Nikita Jagtap
  • September 4, 2020
  • Python
Namespaces and Scope in Python

A namespace is a collection of currently defined symbolic names along with information about the object that each name references. You can think of a namespace as a dictionary in which the keys are the object names and the values are the objects themselves. Every key-value pair maps a name to its corresponding object.

We have four types of namespaces:
  1. Built-In
  2. Global
  3. Enclosing
  4. Local

These have differing lifetimes. As Python executes a program, it creates namespaces as necessary and deletes them when they’re no longer needed. Typically, many namespaces will exist at any given time.

For Free, Demo classes Call: 7507414653
Registration Link: Click Here!

The Built-In Namespace

When we consider built-in namespace it contains the names of all of Python’s built-in objects. These are available at all times when Python is running. To get the list of all objects in the built-in namespace we have following command:

>>>

>>> dir(__builtins__)

[‘ArithmeticError’, ‘AssertionError’, ‘AttributeError’,

 ‘BaseException’,’BlockingIOError’, ‘BrokenPipeError’, ‘BufferError’,

 ‘BytesWarning’, ‘ChildProcessError’, ‘ConnectionAbortedError’,

 ‘ConnectionError’, ‘ConnectionRefusedError’, ‘ConnectionResetError’,

 ‘DeprecationWarning’, ‘EOFError’, ‘Ellipsis’, ‘EnvironmentError’,

 ‘Exception’, ‘False’, ‘FileExistsError’, ‘FileNotFoundError’,

 ‘FloatingPointError’, ‘FutureWarning’, ‘GeneratorExit’, ‘IOError’,

 ‘ImportError’, ‘ImportWarning’, ‘IndentationError’, ‘IndexError’,

 ‘InterruptedError’, ‘IsADirectoryError’, ‘KeyError’, ‘KeyboardInterrupt’,

 ‘LookupError’, ‘MemoryError’, ‘ModuleNotFoundError’, ‘NameError’, ‘None’,

 ‘NotADirectoryError’, ‘NotImplemented’, ‘NotImplementedError’, ‘OSError’,

 ‘OverflowError’, ‘PendingDeprecationWarning’, ‘PermissionError’,

 ‘ProcessLookupError’, ‘RecursionError’, ‘ReferenceError’, ‘ResourceWarning’,

 ‘RuntimeError’, ‘RuntimeWarning’, ‘StopAsyncIteration’, ‘StopIteration’,

 ‘SyntaxError’, ‘SyntaxWarning’, ‘SystemError’, ‘SystemExit’, ‘TabError’,

 ‘TimeoutError’, ‘True’, ‘TypeError’, ‘UnboundLocalError’,

 ‘UnicodeDecodeError’, ‘UnicodeEncodeError’, ‘UnicodeError’,

 ‘UnicodeTranslateError’, ‘UnicodeWarning’, ‘UserWarning’, ‘ValueError’,

 ‘Warning’, ‘ZeroDivisionError’, ‘_’, ‘__build_class__’, ‘__debug__’,

 ‘__doc__’, ‘__import__’, ‘__loader__’, ‘__name__’, ‘__package__’,

 ‘__spec__’, ‘abs’, ‘all’, ‘any’, ‘ascii’, ‘bin’, ‘bool’, ‘bytearray’,

 ‘bytes’, ‘callable’, ‘chr’, ‘classmethod’, ‘compile’, ‘complex’,

 ‘copyright’, ‘credits’, ‘delattr’, ‘dict’, ‘dir’, ‘divmod’, ‘enumerate’,

 ‘eval’, ‘exec’, ‘exit’, ‘filter’, ‘float’, ‘format’, ‘frozenset’,

 ‘getattr’, ‘globals’, ‘hasattr’, ‘hash’, ‘help’, ‘hex’, ‘id’, ‘input’,

 ‘int’, ‘isinstance’, ‘issubclass’, ‘iter’, ‘len’, ‘license’, ‘list’,

 ‘locals’, ‘map’, ‘max’, ‘memoryview’, ‘min’, ‘next’, ‘object’, ‘oct’,

 ‘open’, ‘ord’, ‘pow’, ‘print’, ‘property’, ‘quit’, ‘range’, ‘repr’,

 ‘reversed’, ’round’, ‘set’, ‘setattr’, ‘slice’, ‘sorted’, ‘staticmethod’,

 ‘str’, ‘sum’, ‘super’, ‘tuple’, ‘type’, ‘vars’, ‘zip’]

At start Python interpreter creates the built-in namespace. This namespace remains in existence until the interpreter terminates.

For Free, Demo classes Call: 7507414653
Registration Link: Click Here!

The Global Namespace

The global namespace contains any names defined at the level of the main program. Python creates the global namespace when the main program body starts, and it remains in existence until the interpreter terminates.

The interpreter also creates a global namespace for any module that your program loads with the import statement.

The Local and Enclosing Namespaces

As we know the interpreter creates a new namespace whenever a function executes. This namespace is local to the function and remains in existence until the function terminates. You can also define one function inside another:

>>>

1 >>> def f():

 2    print(‘Start f()’)

 3

 4    def g():

 5        print(‘Start g()’)

 6        print(‘End g()’)

 7        return

 8

 9    g()

10

11    print(‘End f()’)

12    return

13

14 

15 >>> f()

16 Start f()

17 Start g()

18 End g()

19 End f()

 

In this example, function g() is defined within the body of f(). Here’s what’s happening in this code:

  • Lines 1 to 12 define f(), the enclosing function.
  • Lines 4 to 7 define g(), the enclosed function.
  • On line 15, the main program calls f().
  • On line 9, f() calls g().

When the main calls f(), Python will create new namespace for f(). Similarly, when f() calls g(), g() gets its own separate namespace. The namespace created for g() is the local namespace, and the namespace created for f() is the enclosing namespace.

All namespaces remains in existence until its respective function terminates. Python might not immediately reclaim the memory allocated for those namespaces when their functions terminate, but all references to the objects they contain cease to be valid.

Variable Scope

The existence of multiple, distinct namespaces means several different instances of a particular name can exist simultaneously while a Python program runs. As long as each instance is in a different namespace, they’re all maintained separately and won’t interfere with one another.

But that raises a question: Suppose you refer to the name x in your code, and x exists in several namespaces. How does Python know which one you mean?

The answer lies in the concept of scope. The scope of a name is the region of a program in which that name has meaning. The interpreter determines this at runtime based on where the name definition occurs and where in the code the name is referenced.

For Free, Demo classes Call: 7507414653
Registration Link: Click Here!

Python Namespace Dictionaries

In this blog, when namespaces were first introduced, you were encouraged to think of a namespace as a dictionary in which the keys are the object names and the values are the objects themselves. In fact, for global and local namespaces, that’s precisely what they are! Python really does implement these namespaces as dictionaries.

Python provides built-in functions called globals() and locals() that allow you to access global and local namespace dictionaries.

The globals() function

The built-in function globals() returns a reference to the current global namespace dictionary. You can use it to access the objects in the global namespace. Here’s an example of what it looks like when the main program starts:

>>>

>>> type(globals())

<class ‘dict’>

 

>>> globals()

{‘__name__’: ‘__main__’, ‘__doc__’: None, ‘__package__’: None,

‘__loader__’: <class ‘_frozen_importlib.BuiltinImporter’>, ‘__spec__’: None,

‘__annotations__’: {}, ‘__builtins__’: <module ‘builtins’ (built-in)>}

 

As you can see, the interpreter has put several entries in globals() already. Depending on your Python version and operating system, it may look a little different in your environment. But it should be similar.

Now watch what happens when you define a variable in the global scope:

>>>

>>> x = ‘foo’

 

>>> globals()

{‘__name__’: ‘__main__’, ‘__doc__’: None, ‘__package__’: None,

‘__loader__’: <class ‘_frozen_importlib.BuiltinImporter’>, ‘__spec__’: None,

‘__annotations__’: {}, ‘__builtins__’: <module ‘builtins’ (built-in)>,

‘x’: ‘foo’}

 

After the assignment statement x = ‘foo’, a new item appears in the global namespace dictionary. The dictionary key is the object’s name, x, and the dictionary value is the object’s value, ‘foo’.

You would typically access this object in the usual way, by referring to its symbolic name, x. But you can also access it indirectly through the global namespace dictionary:

>>>

1 >>> x

 2 ‘foo’

 3 >>> globals()[‘x’]

 4 ‘foo’

 5 

 6 >>> x is globals()[‘x’]

 7 True

 

The is comparison on line 6 confirms that these are in fact the same object.

You can create and modify entries in the global namespace using the globals() function as well:

>>>

1 >>> globals()[‘y’] = 100

 2 

 3 >>> globals()

 4 {‘__name__’: ‘__main__’, ‘__doc__’: None, ‘__package__’: None,

 5 ‘__loader__’: <class ‘_frozen_importlib.BuiltinImporter’>, ‘__spec__’: None,

 6 ‘__annotations__’: {}, ‘__builtins__’: <module ‘builtins’ (built-in)>,

 7 ‘x’: ‘foo’, ‘y’: 100}

 8 

 9 >>> y

10 100

11 

12 >>> globals()[‘y’] = 3.14159

13 

14 >>> y

15 3.14159

 

The statement on line 1 has the same effect as the assignment statement y = 100. The statement on line 12 is equivalent to y = 3.14159.

It’s a little off the beaten path to create and modify objects in the global scope this way when simple assignment statements will do. But it works, and it illustrates the concept nicely.

The locals() function

Python provides a corresponding built-in function called locals(). It’s similar to globals() but accesses objects in the local namespace instead:

>>>

>>> def f(x, y):

   s = ‘foo’

   print(locals())

 

>>> f(10, 0.5)

{‘s’: ‘foo’, ‘y’: 0.5, ‘x’: 10}

For Free, Demo classes Call: 7507414653
Registration Link: Click Here!

Modify Variables Out of Scope

Sometimes a function can modify its argument in the calling environment by making changes to the corresponding parameter, and sometimes it can’t:

  • An immutable argument can never be modified by a function.
  • A mutable argument can’t be redefined wholesale, but it can be modified in place.

A similar situation exists when a function tries to modify a variable outside its local scope. A function can’t modify an immutable object outside its local scope at all:

>>>

1 >>> x = 20

  2 >>> def f():

 3    x = 40

 4    print(x)

 5

 6 

 7 >>> f()

 8 40

 9 >>> x

10 20

 

A function can modify an object of mutable type that’s outside its local scope if it modifies the object in place:

>>>

>>> my_list = [‘foo’, ‘bar’, ‘baz’]

>>> def f():

   my_list[1] = ‘quux’

>>> f()

>>> my_list

[‘foo’, ‘quux’, ‘baz’]

 

The global Declaration

What if you really do need to modify a value in the global scope from within f()? This is possible in Python using the global declaration:

>>>

>>> x = 20

>>> def f():

   global x

   x = 40

   print(x)

 

>>> f()

40

>>> x

40

 

The global x statement indicates that while f() executes, references to the name x will refer to the x that is in the global namespace. That means the assignment x = 40 doesn’t create a new reference. It assigns a new value to x in the global scope instead:

 

The nonlocal Declaration

A similar situation exists with nested function definitions. The global declaration allows a function to access and modify an object in the global scope. What if an enclosed function needs to modify an object in the enclosing scope? Consider this example:

>>>

1 >>> def f():

 2    x = 20

 3

 4    def g():

 5        x = 40

 6

 7    g()

 8    print(x)

 9

10 

11 >>> f()

12 20

 

In this case, the first definition of x is in the enclosing scope, not the global scope. Just as g() can’t directly modify a variable in the global scope, neither can it modify x in the enclosing function’s scope. Following the assignment x = 40 on line 5, x in the enclosing scope remains 20.

The global keyword isn’t a solution for this situation:

>>>

>>> def f():

   x = 20

   def g():

       global x

       x = 40

   g()

   print(x)

 

>>> f()

20

 

Since x is in the enclosing function’s scope, not the global scope, the global keyword doesn’t work here. After g() terminates, x in the enclosing scope remains 20.

In fact, in this example, the global x statement not only fails to provide access to x in the enclosing scope, but it also creates an object called x in the global scope whose value is 40:

>>>

>>> def f():

   x = 20

   def g():

       global x

       x = 40

   g()

   print(x)

 

>>> f()

20

>>> x

40

For Free, Demo classes Call: 7507414653
Registration Link: Click Here!

To modify x in the enclosing scope from inside g(), you need the analogous keyword nonlocal. Names specified after the nonlocal keyword refer to variables in the nearest enclosing scope:

>>>

1 >>> def f():

 2    x = 20

 3

 4    def g():

 5        nonlocal x

 6        x = 40

 7

 8    g()

 9    print(x)

10

11 

12 >>> f()

13 40

 

After the nonlocal x statement on line 5, when g() refers to x, it refers to the x in the nearest enclosing scope, whose definition is in f() on line 2:

The nonlocal Declaration

The print() statement at the end of f() on line 9 confirms that the call to g() has changed the value of x in the enclosing scope to 40.

Author:
Jagtap, Nikita | SevenMentor Pvt Ltd.

 

Call the Trainer and Book your free demo Class for now!!!

call icon

© Copyright 2019 | Sevenmentor Pvt Ltd.

 

Submit Comment

Your email address will not be published. Required fields are marked *

*
*