Namespace and scopes in python.

Namespaces are a central thought in Python and can be exceptionally useful in organizing and arranging your code (particularly when you are working on a large project). Nonetheless, namespaces may be a to some degree troublesome idea to get a handle on and become accustomed to in case you’re new to programming or in any event, originating from another programming language.

Python Namespace

Namespace is the naming framework to maintain a strategic distance from vagueness and to make name unique. In case you’re acquainted with other programming language (i.e C/C++/Java), you may know the namespace as of now.

However, Python’s namespace is actualized using Python Dictionary. That implies, namespace is basically a key-value pair. For every key, there will be a value. In the accompanying areas, we will talk about names and their space.

What’s a name?

Before beginning with namespaces, you need to understand what Python implies by a name. A name in Python is generally similar to a variable in pretty much some other language, however with a couple of additional items. First of, as a result of Python’s dynamic nature, you can apply a name to pretty much anything. You can obviously give names to values.

x =10
y = "apple"
z = [10,20,30]

You can also have names in function:

def name():
    print("name in namespaces")
name()

Names go hand in hand with Python’s object system, i.e. everything in Python is an object. Numbers, strings, functions, classes are all objects. The best approach to get to the objects is frequently through a name.

Names and their Spaces

If you dissect the word namespace, you will get two things. One is name and another is space. Fundamentally, name is referred to the object name (likewise knows as identifier). That implies, the object you declare is expand the namespace. What’s more, we have told prior that the namespace in python is executed utilizing dictionary. So consider, there are two namespace, class1 and class2

class1 = { 'student1':'John', 'student2':'Mary'}
class2 = {'student1':'John', 'student2':'Mary', 'student3':'Rob' }

Here, you can see that, the names can be identical in both namespace yet they are diverse as object. In this way, namespace permits us to use objects of same name yet in various namespace. The accompanying code will give you the idea of namespace.

name = 2
print('id(2) =', id(2))

print('id(name) =', id(name))

Output

Types of Namespace

At the point when Python interpreter runs exclusively without an user-defined modules, methods, classes, and so forth. A few functions like print(), id() are consistently present, these are built-in namespaces. At the point when a user makes a module, a global namespace gets created, later formation of local functions makes the local namespace. The built-in namespace includes global namespace an they envelops local namespace.

Lifetime of a Namespace

A lifetime of a namespace relies on the scope of objects, if the scope of an object ends, the lifetime of that namespace reaches a conclusion. Thus, it is beyond the realm of imagination to expect to get to inner namespace’s objects from an external namespace.

name1 = 45
def func(): 
    name2 = 60
    def some_func(): 
        name3 = 7

But, sometimes one might need to update the global  variable, which can be done like:

globvar = 5
def method(): 
    global globvar 
    globvar = globvar + 1
    print(globvar) 
method() 

Output

Scope of variables in Python

You will get acquainted with the boundary of variables inside a program – its “scope“. There are four different scopes with the assistance of examples: local, enclosing, global, and built-in. These scopes together structure the basis for the LEGB rule used by the Python interpreter when working with variables. At that point after, you will return to certain examples with additional complexity to open the route for the global keyword followed by the nonlocal keyword.

Variable Scope

Since you realize how to introduce a variable. We should discuss the scope of these scopes. Not all variables can be accessed from anywhere in a program. The piece of a program where a variable is accessible is called its scope. There are four significant types of variable scope and is the reason for the LEGB rule. LEGB represents Local – > Enclosing – > Global – > Built-in.

Let’s get familiar with scopes…

Local Scope

At whatever point you define a variable inside a function, its scope lies ONLY inside the function. It is available from where it is defined until the end of the function and exists for as long as the function is executing (Source). Which implies its value can’t be changed or even accessed to from outside the function.

How about we take a basic model and understand:

def number():
    num = 1
    print("The number defined is: ", num)

number()

print("The number defined is: ", num)

Output

We had the option to print the num variable by calling the function number() (# Print statement 1). But when attempting to access and afterward print the same variable from outside the function (# Print statement 2), it raised a NameError. This is on the grounds that num is “local” to the function – thus, it can’t be reached from outside the function body.

Enclosing Scope

Imagine a scenario in which we have a nested function (function inside another function). How does the scope change? Let’s understand with the help of an example.

def outer():
    num1 = 1
    def inner():
        num2 = 2
        print("num from outer: ", num1)
        print("num from inner: ", num2)
    inner()
    print("num from inner: ", num2)

outer()

Output

This is on the grounds that you can’t get to num2 from outer() (# Print statement 3). It isn’t defined inside that function. However, you can get to num1 from inner() (# Print statement 1), because of the fact that the scope of num1 is larger, it is inside outer().

This is an enclosing scope. Outer variables have a large scope and can be accessed to from the encased function inner()

Global Scope

This is maybe the most effortless scope to understand. At whatever point a variable is defined outside any function, it turns into a global variable, and its scope is anywhere inside the program. Which implies it tends to be used by any function.

greeting = "Hello"

def world():
    world = "Learner"
    print(greeting, world)

def name(name):
    print(greeting, name)

world()
name("Joseph")

Output

Built in Scope

This is the widest scope that exists! All the special reserved keywords fall under this scope. We can call the keywords anywhere within our program without having to define them before use.

Keywords are reserved words in Python. They are case sensitive.

Keywords in Python:

LEGB Rule

Suppose you’re calling print(x) within inner(), which is a function nested in outer(). At that point Python will initially look if “x” was defined locally within inner(). If not, the variable defined in outer() will be utilized. This is the enclosing function. If that it additionally wasn’t defined there, the Python interpreter will go up another level – to the global scope. Over that, you will just locate the built-in scope, which contains special variables reserved for Python itself.

x = 0
def outer():
    x = 1
    def inner():
        x = 2

Scenario 1: Global Scope

Recollect the world() function from prior? Suppose you needed to have the option to change the global variable greeting(“Hello”) to set another value (“Hi”), so that world() prints “Hello Learner”

greeting = "Hello"

def change_greeting(new_greeting):
    greeting = new_greeting

def greeting_world():
    world = "Learner"
    print(greeting, world)

change_greeting("Hi")
greeting_world()

Output

But, this is not the expected output.

This is on the grounds that when we set the value of welcome to “Hi”, it made another local variable greeting in the scope of change_greeting(). It didn’t transform anything for the global greeting. This is where the global keyword proves to be useful.

Global Keyword

With global, you’re advising Python to utilize the all globally defined variable rather than locally creating one. To utilize the keyword, simply type ‘global’, trailed by the variable name.

Let’s understand this on Scenario 1.

greeting = "Hello"

def change_greeting(new_greeting):
    global greeting
    greeting = new_greeting

def greeting_world():
    world = "Learner"
    print(greeting, world)

change_greeting("Hi")
greeting_world()

Output

Scenario 2: Enclosing Scope

Here, we examine the outer() and inner() nested functions from the Enclosing Scope example. Let’s see what happens when we try to change the value of num from 1 to 0 from within inner().

def outer():
    num = 1
    def inner():
        num = 0
        num = 1
        print("inner num is: ", num)
    inner()
    print("outer num is: ", num)

outer()

Output

Nonlocal Keyword

This is another convenient keyword that permits us to work all the more flexibly and flawlessly with variable scopes. The nonlocal keyword is valuable in nested function. It makes the variable refer to the recently bound variable in the nearest enclosing scope. As such, it will keep the variable from attempting to bind locally first, and power it to go a level ‘higher up’. The syntax is like the global keyword.

def outer():
    num1 = 1
    def inner():
        nonlocal num1
        num1 = 0
        num2 = 1
        print("inner num is: ", num2)
    inner()
    print("outer num is: ", num1)

outer()

Output

And, hence you have the desired output!