passing arrays to functions. what is passed? reference ot first? length separately?
Best practice is to name them \(lower\_case\_and\_underscore\).
Everything including functions are objects. As a result functions are first class. Functions can accept functions and can return functions.
def my_function_no_parameters():
    return 0
    
def my_function_with_parameters(x, y):
    return x + y
apply @ function to function below to decorate it. syntactic sugar
def my_decorator(func):
    def inner(a):
        print("Printing ", a, " in a decorated way")
        return
    return inner
    
@my_decorator
def just_printing(a):
    print(a)
def f(a: int = 1, b: int = 2) -> int:
    return a + b
Can have side effects on objects in parameters if mutable.
Can have side effects on global variables if present.
def f(a, b):
    return a + b
Can accept multiple variables with *arguments
l=[1,2]
f(*l)
Or can accept named literals with **kwargs (ie key word arguments)
args = {"a":1, "b":2}
f(**args)
def main():
    // Do stuff
if __name__ == "__main__":
    main()
functions which make generators: yield function
def my_function(x: "annotation of the input variable x") -> "annotation of the return":
    return x
Can but types in annotations. Types are not checked at run time.
def f(a: str, b: str = "apple") -> str:
    return a
defining functions: + triple quote comment at start for documentation
def my_function_no_parameters():
    """
    This is the documentation of my function.
    """
    return 0
my_function = lambda a: a + 1
from functools import partial
def f(a, b):
    return a + b
    
g = partial(f,