Link Search Menu Expand Document

Gist for: function_variables.py

Source Code

# the one and only Dev.E.L'Peer  https://github.com/develpeer
##
# This gist explores how function invocations affect variable scopes
##
from copy import copy, deepcopy

x = 10


def func_in_func():
    outer_var = 99
    print(f"Are outer variables accessible? [outer_var = {x}]")

    def inner_func():
        print(f"Are outer variables accessible? [outer_var = {outer_var}]")
        print(f"Are outer variables accessible? [outer_var = {x}]")

    inner_func()


func_in_func()


def name_collision_variable_hoisting():
    try:
        # this will throw an error because the name collides with
        # global namespace and the var havsnt been initialsed yet
        print(f" During execution:x :{x}")
    except Exception as e:
        print(f"Attempting to use the variable 'x' will throw the error:[{e}]")
    x = 11
    print(f"During execution:x :{x}")


print(f"\n{'=' * 25}\nBefore execution: x :{x}")  # global
name_collision_variable_hoisting()  # local
print(f"After execution: x :{x}")  # global

#
# Do functions get called by value or called by reference
#
print(f"\n{'== ' * 25}\nSimple Dict pass by reference\n{'== ' * 25}")
d1 = {"dev": 1, "eloper": 2}


def modify_reference(d):
    d["dev"] = "this is modifiable"
    if d.get("d1"):
        d["d1"]["dev"] = "Does modifying this work?"


print(f"Value of 'd1' before invocation is:{d1}")
modify_reference(d1)
print(f"Value of 'd1' after invocation is:{d1}")

##
# How does pass by reference impact primitives
##
x = 100
def modify_param(y):
    y=1000
    return y

print("Does pass by reference mess things up for primitives? Of course not",x,modify_param(x),x)

##
# How does pass by reference impact objects
##
z = [100]
def modify_object(y):
    z[0] = 1000
    return z
print("Does pass by reference mess things up for objects? Of course YES!!",x,modify_object(z),z)



##
# Make a deep copy to prevent modification
##
print(f"\n{'== ' * 25}\nSimple Dict pass shallow copy\n{'== ' * 25}")
d2 = {"dev": 1, "eloper": 2}
print(f"Value of 'd2' before invocation is:{d2}")
modify_reference(copy(d2))
print(f"Value of 'd2' after invocation is:{d2}")

print(f"\n{'== ' * 25}\nObject with internal object\n{'== ' * 25}")
d2["d1"] = {"dev": 1, "eloper": 2}
print(f"Value of 'd2' before invocation is:{d2}")
modify_reference(copy(d2))
print(f"Value of 'd2' after invocation is:{d2}")

print(f"\n{'== ' * 25}\nObject with internal object -> do a deep copy\n{'== ' * 25}")
d2["d1"] = {"dev": 1, "eloper": 2}
print(f"Value of 'd2' before invocation is:{d2}")
modify_reference(deepcopy(d2))
print(f"Value of 'd2' after invocation is:{d2}")

##
# local and gloabl "namespaces".. "honking great idea" - my a$$
##
def test_locals():
    inner_var = 10
    print("Globals===>",globals(),"\n"+"===="*20,)
    l = locals()
    print("Locals",l)

    inner_var_2 = 90
    print("Locals and  will now contain 'inner_var_2', So will l. \nlocals()=",locals(),"\nl=", l)

    l["inner_var"],l["inner_var_2"]=0.1,0.9
    print("Can modify values in l:", l["inner_var"],l["inner_var_2"])
    print("But not local variables", inner_var, inner_var_2 )
    
    
test_locals()

Output

After running the above code snippet, you will get this output

>>> Are outer variables accessible? [outer_var = 10]
>>> Are outer variables accessible? [outer_var = 99]
>>> Are outer variables accessible? [outer_var = 10]
>>>
>>> =========================
>>> Before execution: x :10
>>> Attempting to use the variable 'x' will throw the error:[local variable 'x' referenced before assignment]
>>> During execution:x :11
>>> After execution: x :10
>>>
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Simple Dict pass by reference
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Value of 'd1' before invocation is:{'dev': 1, 'eloper': 2}
>>> Value of 'd1' after invocation is:{'dev': 'this is modifiable', 'eloper': 2}
>>> Does pass by reference mess things up for primitives? Of course not 100 1000 100
>>> Does pass by reference mess things up for objects? Of course YES!! 100 [1000] [1000]
>>>
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Simple Dict pass shallow copy
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Value of 'd2' before invocation is:{'dev': 1, 'eloper': 2}
>>> Value of 'd2' after invocation is:{'dev': 1, 'eloper': 2}
>>>
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Object with internal object
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Value of 'd2' before invocation is:{'dev': 1, 'eloper': 2, 'd1': {'dev': 1, 'eloper': 2}}
>>> Value of 'd2' after invocation is:{'dev': 1, 'eloper': 2, 'd1': {'dev': 'Does modifying this work?', 'eloper': 2}}
>>>
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Object with internal object -> do a deep copy
>>> == == == == == == == == == == == == == == == == == == == == == == == == ==
>>> Value of 'd2' before invocation is:{'dev': 1, 'eloper': 2, 'd1': {'dev': 1, 'eloper': 2}}
>>> Value of 'd2' after invocation is:{'dev': 1, 'eloper': 2, 'd1': {'dev': 1, 'eloper': 2}}
>>> Globals===> {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1061a4c10>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/amit/PROJ/pub/bin/../../gistAnotherRepo/src/function_variables.py', '__cached__': None, 'copy': <function copy at 0x1062069e0>, 'deepcopy': <function deepcopy at 0x106237e20>, 'x': 100, 'func_in_func': <function func_in_func at 0x1062068c0>, 'name_collision_variable_hoisting': <function name_collision_variable_hoisting at 0x106244310>, 'd1': {'dev': 'this is modifiable', 'eloper': 2}, 'modify_reference': <function modify_reference at 0x1062443a0>, 'modify_param': <function modify_param at 0x106244430>, 'z': [1000], 'modify_object': <function modify_object at 0x1062444c0>, 'd2': {'dev': 1, 'eloper': 2, 'd1': {'dev': 1, 'eloper': 2}}, 'test_locals': <function test_locals at 0x106244700>}
>>> ================================================================================
>>> Locals {'inner_var': 10}
>>> Locals and will now contain 'inner_var_2', So will l.
>>> locals()= {'inner_var': 10, 'l': {...}, 'inner_var_2': 90}
>>> l= {'inner_var': 10, 'l': {...}, 'inner_var_2': 90}
>>> Can modify values in l: 0.1 0.9
>>> But not local variables 10 90