# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 2
#     language: python
#     name: python2
# ---

# # Functions
# Functions in Python (and other languages in general) are a way to package reusable chunks of code. You define a function using the **def** keyword. They can be simple

def plus_one(x):
    return x + 1


plus_one(1)

plus_one(2)


# or complex

def do_math(x):
    return (x * 2.)/(x - 1.)


do_math(2)

do_math(42)


# You can call functions from within other functions.

# +
def abra(s):
    return "Abra " + s

def alakazam(s):
    return abra(s) + " Alakazam"

alakazam("Kadabra")


# -

# Functions can have lots of arguments/parameters

# +
def all_the_params(a, b, c, d):
    return a + b + c + d

all_the_params(1, 2, 3, 4)


# -

# or no arguments

# +
def toop():
    return "toop"

toop()


# -

# Functions can take pretty much anything as arguments, including other functions.

# +
def call_it_twice(fun):
    return fun() + " " + fun()

call_it_twice(toop)


# -

# Functions can also return functions

# +
def print_it_twice(number):
    def x():
        return number + " " + number
    return x

a_fun = print_it_twice("Double")
a_fun
# -

# Here ```a_fun``` is a function which you can call like any other function

a_fun()


# ## Default Values
# You can give a function default values for its arguments.

def with_defaults(x, y = 2, z = 3):
    print(x, y, z)
    return x + y + z


with_defaults(1) # Using both default values.

with_defaults(1, 1) # Using one default value.

with_defaults(1, 1, 1) # Using no default values.

# If you wanted to change only ```z``` but not ```y``` you can used a ```keyword argument```.

with_defaults(1, z = 4) # Use the default value for y but not z.


# This is useful for dealing with functions with lots of default values when you only want to change some of the defaults.

# ## Recursions
# Functions can also call themselves. This is known as recursion.

# +
def deeper():
    return deeper()

deeper()


# -

# Though unless your function stops calling itself you're going to have issues.

# +
def sum_up_to(x):
    print(x)
    if x <= 0: return 0
    else: return x + sum_up_to(x - 1) 

sum_up_to(4)


# -

# ## Function Gotchas
# If you forget to include the ```return``` keyword your function will return ```None```.

# +
def no_return(x):
    x + x

a_thing = no_return(42)
print(a_thing)
# -

# If you forget to include ```()``` when calling a function

print(toop) # Incomplete

# you get back the function itself as opposed to the result of the function

StarcoderdataJupyterScriptsDedupFiltered