<jupyter_start><jupyter_text>What You Will Learn- What is a function- When to use functions- How to pass parameters into functions- Tuples- Mutable vs immuntable variables- Funcions returning tuples  What is a functionIn math class we learned that we can define fucntions such as f(x)=x+1. Therefore f(2)=2+1=3.This is kinda how functions work in Python. You define inputs like x, specify the behavior of the function, like 2+1, and that will producne an output, in this case 3.Let's see this in code<jupyter_code>def f(x):
    return x + 1

result = f(2)
print(result)
def getAverage(gradeList):
    gradeSum = sum(gradeList)
    n = len(gradeList)
    
    avg = gradeSum / n
    
    return avg

gradeList = [85, 73, 91, 60]

avgGrade = getAverage(gradeList)

print(avgGrade)<jupyter_output>77.25<jupyter_text>Note    -If we do not spesifcy a return then the function will return None<jupyter_code>def getAverage(gradeList):
    gradeSum = sum(gradeList)
    n = len(gradeList)
    
    avg = gradeSum / n
    
avgGrade = getAverage(gradeList)

print(avgGrade)
print(type(avgGrade))<jupyter_output>None
<class 'NoneType'><jupyter_text>Note- In some cases you will have functions that don't need to return anything<jupyter_code>def raiseAlarm():
    print("The Alarm Is Now active")
    # in a real program that was setting an alarm high this would be calling other functions

raiseAlarm()<jupyter_output>The Alarm Is Now active<jupyter_text>Note - Something interesting happens when we do not put the '()' after a function name<jupyter_code>def raiseAlarm():
    print("The Alarm Is Now active")
    # in a real program that was setting an alarm high this would be calling other functions

raiseAlarm<jupyter_output><empty_output><jupyter_text>Here we pointed to the function called raiseAlarm but we did call it  When to use a fucntionUntil know we had code "free floating". This is bad. Don't do it. For each "thing" you need to do, there should be a new function.We do this to allow for Modular Design:- Break a problem up into smaller chuncks. Each small chunk should be its own function. This helps you plan a work flow for code- For larger projects de-bugging is a major issue. If you have 10,000+ lines of code and something breaks you need a way to dig back though and find where an error is occuring. Hence, modular design- If we have modular designs then we can reuse code over again for other projects- Read-ablity. Has anyone opened up a MATLAB script and it has over 200 lines and it just is so confusing to read... Imagine if Google write all 2 billion lines of code for its search engine like that? How long would that on-boarding take?    - Using functions is kinda like in 4th grade when papers first starting using paragraphs. Your work is getting to the point more modularity is needed or else no one will be able to read it.  ExampleAbove we solved the average of a list of grades. Did you notice we get the length of that list and the sum of that list from two diffrent function calls? ...Let's rewrite that code but this time we write those calls ourselves.<jupyter_code>def getSum(gradeList):
    s = 0
    for grade in gradeList:
        s += grade
        
    return s


def getLegnth(gradeList):
    l = 0
    for grade in gradeList:
        l += 1
        
    return l


def getAverage(gradeList):
    gradeSum = getSum(gradeList)
    length = getLegnth(gradeList)
    
    return gradeSum / length

gradeList = [85, 73, 91, 60]

avgGrade = getAverage(gradeList)

print(avgGrade)<jupyter_output>309
4
77.25<jupyter_text>What if we need something like f(x,y) that has two inputs?<jupyter_code>def maths(x, y):
    return x / y

print(maths(6, 3))<jupyter_output>2.0<jupyter_text>Note- Here the order is import that we pass in varables. What happens if we pass in 3 then 6 ...<jupyter_code>def maths(x, y):
    return x / y

print(maths(3, 6))<jupyter_output>0.5<jupyter_text>Note- We can also define inputs to match an expected varable<jupyter_code>def maths(x, y):
    return x / y

print(maths(y=3, x=6))<jupyter_output>2.0<jupyter_text>Note- You can also set default values that can be overwritten<jupyter_code>def maths(x=6, y=3):
    return x / y

print(maths())
def maths(x=6, y=3):
    return x / y

print(maths(x=100, y=5)) # overwrites default vales in maths()<jupyter_output>20.0<jupyter_text>Global Varables vs Function varablesSo right now we have two diffrent types of varables1) Global varables    - meaning everything can see this varable2) Function     - the varable can only exist in the function<jupyter_code>X = 5

def test():
    for i in range(X): # global varable being accessed in a function
        print(i)
def example():
    bigVAl = 99999999
    
def thingy():
    print(bigVAl)  # will this throw an error? If so why and what kind?<jupyter_output><empty_output><jupyter_text>Note- Finish the code in the two examples above to call test() and thingy() and see what happens  TuplesIn python a tuple is like a list with one change: once it is set it cannot change. This is called an immutable object. Compared to a list whos content can be altered - that is a mutable object.Let's see it in action<jupyter_code>example = "test", 85, False

print(example)
print(type(example))

for item in example:
    print(f"\n{item}")
example = "test", 85, False

# given this tubple we can unpack it into 3 varables

x, y, z = example

print(x)
print(y)
print(y)
# this unpacking alows us to return multilbe things from a function

def getSum(inputList):
    s = 0
    for item in inputList:
        s += item
        
    return s


def getLegnth(inputList):
    l = 0
    for item in inputList:
        l += 1
        
    return l


def getListInfo(gradeList):
    exSum = getSum(gradeList)
    exLength = getLegnth(gradeList)
    
    return exSum, exLength
    

exampleList = [85, 73, 91, 60, 72, 29, 1, 47]

listSum, listLength = getListInfo(exampleList)

print(listSum)
print(listLength)
def makeQuerry(name):
    fakeDataBase = {
        "<NAME>": {
            "Popuation": 165000,
            "Zip Code": "80521–80528"
        }
    }
    
    return fakeDataBase.get(name)

def getCityInfo(name):
    querry = makeQuerry(name)
    
    if querry:
        return querry.get("Popuation"), querry.get("Zip Code")
    
def dataBaseRequest(name):
    population, zipCodeRange = getCityInfo(name)
    
    if population:
        print(f"{name} has a population of {population}")
    
    if zipCodeRange:
        print(f"{name} has a zip-code range of {zipCodeRange}")
        
dataBaseRequest(name="<NAME>")<jupyter_output><NAME> has a population of 165000
Fort Collins has a zip-code range of 80521–80528
{'fraction_non_alphanumeric': 0.07903831705484599, 'fraction_numerical': 0.02133734034560481, 'mean_word_length': 3.6190145732130463, 'pattern_counts': {'":': 3, '<': 40, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '564200', 'license': 'MIT', 'n_tokens_mistral': 2221, 'n_tokens_neox': 2088, 'n_words': 854, 'path': 'Python_Jupyter_Training/Week_2/2_Functions.ipynb', 'repo_name': 'brendo61-byte/pythonOnBoarding', 'size': '6673'}