<jupyter_start><jupyter_text>Python the basics: functions> *DS Data manipulation, analysis and visualisation in Python*  > *December, 2017*> *© 2016, <NAME> and <NAME>  (, ). Licensed under [CC BY 4.0 Creative Commons](http://creativecommons.org/licenses/by/4.0/)*---> This notebook is largely based on material of the *Python Scientific Lecture Notes* (https://scipy-lectures.github.io/), adapted with some exercises.  Functions  Function definition Function blocks must be indented as other control-flow blocks<jupyter_code>def the_answer_to_the_universe():
    print(42)

the_answer_to_the_universe()<jupyter_output><empty_output><jupyter_text>**Note**: the syntax to define a function:* the def keyword;* is followed by the function’s name, then* the arguments of the function are given between parentheses followed by a colon.* the function body;* and return object for optionally returning values.  Return statement Functions can *optionally* return values<jupyter_code>def calcAreaSquare(edge):
    return edge**2
calcAreaSquare(2.3)<jupyter_output><empty_output><jupyter_text>Parameters Mandatory parameters (positional arguments)<jupyter_code>def double_it(x):
    return 2*x

double_it(3)
#double_it()<jupyter_output><empty_output><jupyter_text>Optional parameters (keyword or named arguments)The order of the keyword arguments does not matter, but it is good practice to use the same ordering as the function's definition*Keyword arguments* are a very convenient feature for defining functions with a variable number of arguments, especially when default values are to be used in most calls to the function.<jupyter_code>def double_it (x=1):
    return 2*x

print(double_it(3))
print(double_it())
def addition(int1=1, int2=1, int3=1):
    return int1 + 2*int2 + 3*int3

print(addition(int1=1, int2=1, int3=1))
print(addition(int1=1, int3=1, int2=1)) # sequence of these named arguments do not matter<jupyter_output><empty_output><jupyter_text>NOTE:         Default values are evaluated when the function is defined, not when it is called. This can be problematic when using mutable types (e.g. dictionary or list) and modifying them in the function body, since the modifications will be persistent across invocations of the function.Using an immutable type in a keyword argument:<jupyter_code>bigx = 10
def double_it(x=bigx):
    return x * 2
bigx = 1e9
double_it()<jupyter_output><empty_output><jupyter_text>Using an mutable type in a keyword argument (and modifying it inside the function body)<jupyter_code>def add_to_dict(args={'a': 1, 'b': 2}):
    for i in args.keys():
        args[i] += 1
    print(args)
add_to_dict
add_to_dict()
add_to_dict()
add_to_dict()
#the {'a': 1, 'b': 2} was created in the memory on the moment that the definition was evaluated
def add_to_dict(args=None):
    if not args:
        args = {'a': 1, 'b': 2}
        
    for i in args.keys():
        args[i] += 1
        
    print(args)
add_to_dict
add_to_dict()
add_to_dict()<jupyter_output><empty_output><jupyter_text>Variable number of parameters Special forms of parameters:* *args: any number of positional arguments packed into a tuple* **kwargs: any number of keyword arguments packed into a dictionary<jupyter_code>def variable_args(*args, **kwargs):
    print('args is', args)
    print('kwargs is', kwargs)

variable_args('one', 'two', x=1, y=2, z=3)<jupyter_output><empty_output><jupyter_text>Docstrings Documentation about what the function does and its parameters. General convention:<jupyter_code>def funcname(params):
    """Concise one-line sentence describing the function.
    
    Extended summary which can contain multiple paragraphs.
    """
    # function body
    pass

funcname?<jupyter_output><empty_output><jupyter_text>Functions are objects Functions are first-class objects, which means they can be:* assigned to a variable* an item in a list (or any collection)* passed as an argument to another function.<jupyter_code>va = variable_args
va('three', x=1, y=2)<jupyter_output><empty_output><jupyter_text>Methods Methods are functions attached to objects. You’ve seen these in our examples on lists, dictionaries, strings, etc...Calling them can be done by dir(object): -------------------------------<jupyter_code>dd = {'antea': 3, 'IMDC': 2, 'arcadis': 4, 'witteveen': 5, 'grontmij': 1}<jupyter_output><empty_output><jupyter_text>EXERCISE: Make a function of the exercise in the previous notebook: Given the dictionary `dd`, check if a key is already existing in the dictionary and raise an exception if the key already exist. Otherwise, return the dict with the element added.<jupyter_code># %load _solutions/03-functions19.py
check_for_key(dd, 'deme')
#check_for_key(dd, 'antea') # uncomment this line<jupyter_output><empty_output><jupyter_text>-----------------------------  Object oriented Programming Wondering what OO is? A very nice introduction is given here: http://py.processing.org/tutorials/objects/ Python supports object-oriented programming (OOP). The goals of OOP are:* to organize the code, and* to re-use code in similar contexts. Here is a small example: we create a Student class, which is an object gathering several custom functions (**methods**) and variables (**attributes**), we will be able to use:<jupyter_code>class Employee():  #object
    
    def __init__(self, name, wage=60.):
        """
        Employee class to save the amount of hours worked and related earnings
        """
        self.name = name
        self.wage = wage
        
        self.hours = 0.        
        
    def worked(self, hours):
        """add worked hours on a project
        """
        try:
            hours = float(hours)
        except:
            raise Exception("Hours not convertable to float!")
            
        self.hours += hours
        
    def calc_earnings(self):
        """
        Calculate earnings
        """
        return self.hours *self.wage
bert = Employee('bert')
bert.worked(10.)
bert.worked(20.)
bert.wage = 80.
bert.calc_earnings()
dir(Employee)<jupyter_output><empty_output><jupyter_text>It is just the same al all the other objects we worked with! ---------------------------------------     EXERCISE: Extend the class `Employee` with a projects attribute, which is a dictionary. Projects can be added by the method `new_project`. Hours are contributed to a specific project<jupyter_code># %load _solutions/03-functions27.py
bert = Employee('bert')
bert.new_project('vmm')
bert.worked(10., 'vmm')
bert.calc_earnings()
bert.new_project('pwc')
bert.info()
bert.worked(3., 'pwc')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11241075497493544, 'fraction_numerical': 0.01336776545647881, 'mean_word_length': 4.2253968253968255, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 59, 'https://': 1, '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': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '325585', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2019, 'n_tokens_neox': 1920, 'n_words': 692, 'path': 'notebooks/python_recap/03-functions.ipynb', 'repo_name': 'rprops/Python_DS-WS', 'size': '6606'}