a = 10

def test():
    print(a)
    a = 12 

test()
print(a)

a = 10

def test():
    a = 12 
    print(a)

test()
print(a)

a = 10

def test():
    """
    - Only for viewing value of Global Variable
    - Cannot change the global variable
    """
    print(a)

test()
print(a)

a = 10

def test():
    """
    - When you need to update the value of Global variable
    """
    global a
    print(a)
    a = 12
    print(a)
    print(id(a))
#     print(locals())
#     print(globals())

print(a)
test()
print(id(a))
print(a)

def test():
    """
    Updating the data
    """
    global glb
    print(glb)
    glb = 12
    
test()
glb = 10
print(glb)

def test():
    """
    Updating the data
    """
    global glb
    print(glb)
    glb = 12

glb = 10    
test()
print(glb)

def test():
    """
    Updating the data
    """
    global glb1
    glb1 = 12
    print(glb1)
   
test()
print(glb)

a = 10

def test():
    global a
    a = "Chennai Riders"
    print(a)
    a = "Pune Rocks"

test()
print(a)

global a
a = 10

def test():
    a = "Pune Rocks"
    print(a)
    print(locals())
    print("~"*20)
#     print(globals())
    
test()
print(a)

a = 10

def test(a):
    print(a)
    a = "Pune Rocks"
    print(locals())
    return a
    
a= test(a)
print(a)
print(len(locals()))
print(len(globals()))

def addlist(lists):
    """
    Add lists of lists, recursively
    the result is global
    """
    global add
    for item in lists:
        if isinstance(item, list): # If item type is list
            addlist(item)
        else:
            add += item # add = add + item

add = 0
addlist([[1, 2], [3, 4, 5], 6])

print(add)

# add = 10

def addlist(lists):
    """
    Add lists of lists, recursively
    the result is global
    """
    global add2
    
    for item in lists:
        if isinstance(item, list): # If item type is list
            addlist(item)
        else:
            if 'add2' in globals():
                add2 += item
            else:
                print("Creating add")
                add2 = item

addlist([[1, 2], [3, 4, 5], 6])

print(add2)

"""
Explanation: Chapter 7: Scope of names

The scope of names (variables) are maintained by Namespaces, which are dictionaries containing the names of the objects (references) and the objects themselves.
As we have seen that names are not pre-defined thus Python uses the code block of the assignment of a name to associate it with a particular namespace. In other words, the place where you assign a name in your source code determines its scope of visibility.
Python uses lexical scoping, which means that variable scopes are determined entirely by their locations in the source code and not by function calls. 
Rules for names inside Functions are as follows 

Names assigned inside a def can only be seen by the code within that def and cannot be referred from outside the function.
Names assigned inside a def do'nt clash with variables from outside the def. i.e. a name assigned outside a def is a completely different variable from a name assigned inside that def.
If a variable is assigned outside all defs, then it is global to the entire file and can be accessed with the help of global keyword inside the def.

Normally, the names are defined in two dictionaries, which can be accessed through the functions locals() and globals(). These dictionaries are updated dynamically at <span class="note" title="Although the dictionaries returned by locals() and globals() can be changed directly, this should be avoided because it can have undesirable effects.">runtime</span>.

Global variables can be overshadowed by local variables (because the local scope is consulted before the global scope). To avoid this, you must declare the variable as global in the local scope.
example:
End of explanation
"""


#add = 10

def addlist(lists):
    """
    Add lists of lists, recursively
    the result is global
    """
    global add
    
    for item in lists:
        if isinstance(item, list): # If item type is list
            addlist(item)
            x = 100
        else:
            add += item
            
        print(x)

addlist([[1, 2], [3, 4, 5], 6])

print(add)

def outer():
    a = 0
    b = 1

    def inner():
        print(a)
        print(b)

    inner()
    
outer()

"""
Explanation: Using global variables is not considered a good development practice, as they make the system harder to understand, so it is better to avoid their use. The same applies to overshadowing variables.
End of explanation
"""


def outer():
    a = 0
    b = 1

    def inner():
        print(a)
        print(b)
        b = 4

    inner()

outer()

def outer():
    a = 0
    print("outer: ", a)
    
    def inner():
        global a
        print("inner: ", a)
        
    inner()

a = 200
b = 10
outer()
print("base")
print(a)
print(b)

def List_fun(l, a=[]):
    """function takes 2 parameters list having values and empty list."""

    for i in l:
        #checking whether the values are list or not
        if isinstance(i, list):
            List_fun(i, a)
        else:
            a.append(i)
    return a 

b=[]
l2 = List_fun([[1,2],[3,[4,5]],6,7], b)
print(l2)
print(b)
print(id(l2))
print(id(b))

def List_fun(l, a=[]):
    """function takes 2 parameters list having values and empty list."""
    
    for i in l:
        #checking whether the values are list or not
        if isinstance(i, list):
            List_fun(i, a)
        else:
            a.append(i)


b=[]
List_fun([[1,2],[3,[4,5]],6,7], b)
print(b)

def fun_numbers(a):
    print(a)
    print(id(a))
    a += 10
    print(a)
    print(id(a))

b = 10
print(id(b))
fun_numbers(b)
print(b)

def fun_numbers(a):
    print(a)
    print(id(a))
    a = [20]
    print(a)
    print(id(a))

b = [10]
print(id(b))
fun_numbers(b)
print(b)

def fun_numbers(a):
    print(a)
    print(id(a))
    a.append(20)
    print(a)
    print(id(a))

b = [10]
print(id(b))
fun_numbers(b)
print(b)

def fun_numbers(a):
    print(a)
    a.append(120)
    print(a)

b = [10]
fun_numbers(b)
print(b)

def func():
    a = 10
    for d in [10,20,30]:
        a = a+d
    print(a)
    
func()

"""
Explanation: NOTE: - A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects.
End of explanation
"""


