################################
### REFERENCES ###
################################
# Python uses references whenever a variable must store values of mutable data types, such as lists or dictionaries. For values with immutable
# data types such as strings, integers, or tuples, Python will store the value itself into the variable.
# When you set one variable equal to another, it duplicates the value into the second variable, but the variables remain separate entities.
firstVariable = 42
secondVariable = firstVariable # set secondVariable using value from firstVariable
firstVariable = 100 # change value of firstVariable (doesn't affect secondVariable)
firstVariable # result is 100, since this was changed
secondVariable # result is 42, still 42 since this wasn't changed when firstVariable was changed
# However, lists don't function in this way. When you assign a list to a variable, the variable holds a *reference* to the list,
# but doesn't actually copy the list value over. If you change the list, it'll change all of the variables that reference that list.
firstVariable = [0, 1, 2, 3, 4]
secondVariable = firstVariable # place firstVariable *reference* into secondVariable, but doesn't actually copy over values
firstVariable[3] = 100000 # change firstVarable, will also affect secondVariable since that variable references the same list
firstVariable # result is [0, 1, 2, 100000, 4]
secondVariable # result is [0, 1, 2, 100000, 4]
################################
### PASSING REFERENCES ###
################################
# References are important for understanding how arguments get passed to functions. When a function is called, the values of the arguments
# are copied to the parameter variables. For lists and dictionaryies, this means a copy of the references is used in the parameter.
def myFunction(someParameter): # define function myFunction with argument someParameter
someParameter.append('hello') # when function is called, append value of 'hello'
myList = [1, 2, 3] # set myList to this list
myFunction(myList) # call myFunction, passing in myList as the argument, at which point the function will append 'hello'
print(myList) # result is [1, 2, 3, 'hello'], note that 'hello' has been appended to myList, since a reference (and not the actual values) was passed to the function
# While passing around references is often the handiest way to deal with lists and dictionaries, if the function modifies the list or
# dicionary that is passed, you may not want these changes in the original list or dictionary value. For this, Python provides a module
# called copy that provides both the copy() and deepcopy() functions. The first of these, copy.copy(), can be used to make a duplicate
# copy of a mutable value like a list or dictionary, and not just a copy of a reference.
# Remember that this happened when a reference was passed:
firstVariable = [0, 1, 2, 3, 4]
secondVariable = firstVariable # place firstVariable *reference* into secondVariable, but doesn't actually copy over values
firstVariable[3] = 100000 # change firstVarable, will also affect secondVariable since that variable references the same list
firstVariable # result is [0, 1, 2, 100000, 4]
secondVariable # result is [0, 1, 2, 100000, 4]
# However, if you use copy.copy():
import copy # import the copy library
firstVariable = [0, 1, 2, 3, 4]
secondVariable = copy.copy(firstVariable) # copy the list from firstVariable into secondVariable (i.e. don't pass the reference)
firstVariable[3] = 100000 # change firstVarable, will will *not* affect secondVariable since that variable used copy.copy()
firstVariable # result is [0, 1, 2, 100000, 4]
secondVariable # result is [0, 1, 2, 3, 4], since secondVariable was copied instead of a reference being passed, and therefore it wasn't changed when firstVariable was changed