<jupyter_start><jupyter_text>[Table of contents](../toc.ipynb) Functions* Functions are very powerful to organize your code and to avoid redundant code.* To work with functions instead of copy and paste is the first step from spaghetti code to clean code.* We will talk about clean code later, but one principle is:  **A function does one thing!**  Basic syntaxUser defined functions follow this syntax.```pythondef function_name(argument):    """Doc string."""    code    return something```<jupyter_code># Here a simple function
def my_print(number):
    print("My text", number)
# Now let's call the function
my_print(3)<jupyter_output>My text 3<jupyter_text>ArgumentsThere are* Required arguments* Keyword arguments* Default arguments* Arguments of variable lengthThe number in the previous function was a required argument. If you forget it, a syntax error occurs.<jupyter_code># Here the required argument is missing
try:
    my_print()  # This does not work
except Exception as error:
    print(error)<jupyter_output>my_print() missing 1 required positional argument: 'number'<jupyter_text>Here a function with two arguments<jupyter_code>def my_div(number, denom):
    return number / denom


# And let's call it with the keywords
my_div(number=15., denom=3.)
# This works also if we flip the keywords
my_div(denom=3., number=15.)<jupyter_output><empty_output><jupyter_text>Functions with default arguments can either be called with or without the default.<jupyter_code>def my_scale(num, mult=6):
    return num * mult
my_scale(3)
my_scale(3, 2)
my_scale(mult=3, num=2)<jupyter_output><empty_output><jupyter_text>Variable length argumentsHere and there, you want to wish to write a function, where you don't know the number of arguments or keyword arguments beforehand.* The `*args` and `**kwargs` collect all arguments and keyword arguments.* Every argument which is prefixed with a `*` is converted to a sequence.* Every argument which is prefixed with two `**` is converted to a dictionary.* The `*args` and `**kwargs` names are just convention, you can take other names.<jupyter_code>def print_variable_args(*args, **kwargs):
    print("Here the args", args)
    print("Here the kwargs", kwargs)
print_variable_args(0, 1, 2, kwarg_one=3, kwarg_two=15)
print_variable_args(0, kwarg_one=3, kwarg_two=15, kwarg_three=-3, )<jupyter_output>Here the args (0,)
Here the kwargs {'kwarg_one': 3, 'kwarg_two': 15, 'kwarg_three': -3}<jupyter_text>Multiple returns* Although not very encouraged by clean coding principles, some programmers like to return multiple variables.* This can be done by `return var1, var2 `.* The return will be a tuple.<jupyter_code>def mult_return(x, y):
    return x**2, y**2


mult_return(3, 5)<jupyter_output><empty_output><jupyter_text>As the return becomes a tuple, you can also use indexing to extract just one value.<jupyter_code># First return
mult_return(3, 5)[0]
# Second return
mult_return(3, 5)[1]<jupyter_output><empty_output><jupyter_text>Anonymous functions* Lambda functions are a nice short hand notation for one liner functions.* Syntax: `lambda arg, arg: expression`* They behave differently form functions defined by `def` keyword.  * They can just return one value.  * They cannot operate multiple expressions.  * They cannot access global variables.* Lambda function are often used if you want to pass a function to a function.<jupyter_code>my_sum = lambda x, y: x + y
diff = lambda x, y: x - y

def my_print(x, y, func):
    print(func(x, y))
my_print(2, 2, func=my_sum)
my_print(2, 2, func=diff)<jupyter_output>0<jupyter_text>However, according to pep8 standard, lambda functions should be avoided and this is easily done with a `def` statement in one line.<jupyter_code>my_sum = lambda x, y: x + y
# can be rewritten as
def my_sum(x, y): return x + y<jupyter_output><empty_output><jupyter_text>Type hints* Starting from Python 3.5, there are type hints which tell other developers or code checkers what kind of variable type a function wants to get.* Type hints are good for code checkers, code documentation, and development tools.* Type hints are rather good for large projects with many different developers.* They are optional.A function with a type hint looks like:<jupyter_code>def greeting(name: str) -> str:
    print('Hello ', name)<jupyter_output><empty_output><jupyter_text>Here, the `(name: str) -> str:` tells that this function wants to see strings.This does not mean that cannot pass "wrong" data type.<jupyter_code>greeting(0)
greeting("Peter")<jupyter_output>Hello  Peter<jupyter_text>Exercise: Function  (10 minutes)* Write a function, which returns the euclidian distance of the two points:  $p=[2, 3, -1]$ and $q=[4, 1, -2]$. * You should just use two arguments at maximum.**Hint*** The result is 3.  SolutionPlease find one possible solution in [`solution_function.py`](solution_function.py) file.<jupyter_code>import sys
sys.path.append("01_basic-python")

from solution_function import *

euclid(p=[2, 3, -1], q=[4, 1, -2])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09705648369132856, 'fraction_numerical': 0.013723150357995227, 'mean_word_length': 5.6697612732095495, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '299025', 'license': 'MIT', 'n_tokens_mistral': 1562, 'n_tokens_neox': 1504, 'n_words': 608, 'path': '01_basic-python/04_functions.ipynb', 'repo_name': 'updtv/py-algorithms-4-automotive-engineering', 'size': '5028'}