# Python Programming/Functions

## Function Calls

A *callable object* is an object that can accept some arguments (also
called parameters) and possibly return an object (often a tuple
containing multiple objects).

A function is the simplest callable object in Python, but there are
others, such as classes or
certain class instances.

#### Defining Functions

A function is defined in Python by the following format:

``` python
def functionname(arg1, arg2, ...):
    statement1
    statement2
    ...
```

``` python
>>> def functionname(arg1,arg2):
...     return arg1+arg2
...
>>> t = functionname(24,24) # Result: 48
```

If a function takes no arguments, it must still include the parentheses,
but without anything in them:

``` python
def functionname():
    statement1
    statement2
    ...
```

The arguments in the function definition bind the arguments passed at
function invocation (i.e. when the function is called), which are called
actual parameters, to the names given when the function is defined,
which are called formal parameters. The interior of the function has no
knowledge of the names given to the actual parameters; the names of the
actual parameters may not even be accessible (they could be inside
another function).

A function can \'return\' a value, for example:

``` python
def square(x):
    return x*x
```

A function can define variables within the function body, which are
considered \'local\' to the function. The locals together with the
arguments comprise all the variables within the scope of the function.
Any names within the function are unbound when the function returns or
reaches the end of the function body.

You can **return multiple values** as follows:

``` python
def first2items(list1):
  return list1[0], list1[1]
a, b = first2items(["Hello", "world", "hi", "universe"])
print(a + " " + b)
```

Keywords: returning multiple values, multiple return values.

### Declaring Arguments

When calling a function that takes some values for further processing,
we need to send some values as Function **Arguments**. For example:

``` python
>>> def find_max(a,b):
   if(a > b):
      return str(a) + " is greater than " + str(b)
   elif(b > a):
      return str(b) + " is greater than " + str(a)
>>> find_max(30, 45)  #Here (30, 45) are the arguments passing for finding max between this two numbers
The output will be: 45 is greater than 30
```

#### Default Argument Values

If any of the formal parameters in the function definition are declared
with the format \"arg = value,\" then you will have the option of not
specifying a value for those arguments when calling the function. If you
do not specify a value, then that parameter will have the default value
given when the function executes.

``` python
>>> def display_message(message, truncate_after=4):
...     print(message[:truncate_after])
...
>>> display_message("message")
mess
>>> display_message("message", 6)
messag
```

Links:

-   4.7.1. Default Argument
    Values,
    The Python Tutorial, docs.python.org

#### Variable-Length Argument Lists

Python allows you to declare two special arguments which allow you to
create arbitrary-length argument lists. This means that each time you
call the function, you can specify any number of arguments above a
certain number.

``` python
def function(first,second,*remaining):
    statement1
    statement2
    ...
```

When calling the above function, you must provide value for each of the
first two arguments. However, since the third parameter is marked with
an asterisk, any actual parameters after the first two will be packed
into a tuple and bound to \"remaining.\"

``` python
>>> def print_tail(first,*tail):
...     print(tail)
...
>>> print_tail(1, 5, 2, "omega")
(5, 2, 'omega')
```

If we declare a formal parameter prefixed with *two* asterisks, then it
will be bound to a dictionary containing any keyword arguments in the
actual parameters which do not correspond to any formal parameters. For
example, consider the function:

``` python
def make_dictionary(max_length=10, **entries):
    return dict([(key, entries[key]) for i, key in enumerate(entries.keys()) if i < max_length])
```

If we call this function with any keyword arguments other than
max_length, they will be placed in the dictionary \"entries.\" If we
include the keyword argument of max_length, it will be bound to the
formal parameter max_length, as usual.

``` python
>>> make_dictionary(max_length=2, key1=5, key2=7, key3=9)
{'key3': 9, 'key2': 7}
```

Links:

-   4.7.3. Arbitrary Argument
    Lists,
    The Python Tutorial, docs.python.org

#### By Value and by Reference

Objects passed as arguments to functions are passed *by reference*; they
are not being copied around. Thus, passing a large list as an argument
does not involve copying all its members to a new location in memory.
Note that even integers are objects. However, the distinction of *by
value* and *by reference* present in some other programming languages
often serves to distinguish whether the passed arguments can be
*actually changed* by the called function and whether the *calling
function can see the changes*.

Passed objects of *mutable* types such as lists and dictionaries can be
changed by the called function and the changes are visible to the
calling function. Passed objects of *immutable* types such as integers
and strings cannot be changed by the called function; the calling
function can be certain that the called function will not change them.
For mutability, see also Data
Types chapter.

An example:

``` python
def appendItem(ilist, item):
  ilist.append(item) # Modifies ilist in a way visible to the caller

def replaceItems(ilist, newcontentlist):
  del ilist[:]                 # Modification visible to the caller
  ilist.extend(newcontentlist) # Modification visible to the caller
  ilist = [5, 6] # No outside effect; lets the local ilist point to a new list object,
                 # losing the reference to the list object passed as an argument
def clearSet(iset):
  iset.clear()

def tryToTouchAnInteger(iint):
  iint += 1 # No outside effect; lets the local iint to point to a new int object,
            # losing the reference to the int object passed as an argument
  print("iint inside:",iint) # 4 if iint was 3 on function entry 

list1 = [1, 2]
appendItem(list1, 3)
print(list1) # [1, 2, 3]
replaceItems(list1, [3, 4])
print(list1) # [3, 4]
set1 = set([1, 2])
clearSet(set1 )
print(set1) # set([])
int1 = 3
tryToTouchAnInteger(int1)
print(int1) # 3
```

### Preventing Argument Change

An argument cannot be declared to be constant, not to be changed by the
called function. If an argument is of an immutable type, it cannot be
changed anyway, but if it is of a mutable type such as list, the calling
function is at the mercy of the called function. Thus, if the calling
function wants to make sure a passed list does not get changed, it has
to pass a copy of the list.

An example:

``` python
def evil_get_length(ilist):
  length = len(ilist)
  del ilist[:] # Muhaha: clear the list
  return length

list1 = [1, 2]
print(evil_get_length(list1[:])) # Pass a copy of list1
print(list1) # list1 = [1, 2]
print(evil_get_length(list1)) # list1 gets cleared
print(list1) # list1 = []
```

### Calling Functions

A function can be called by appending the arguments in parentheses to
the function name, or an empty matched set of parentheses if the
function takes no arguments.

``` python
foo()
square(3)
bar(5, x)
```

A function\'s return value can be used by assigning it to a variable,
like so:

``` python
x = foo()
y = bar(5,x)
```

As shown above, when calling a function you can specify the parameters
by name and you can do so in any order

``` python
def display_message(message, start=0, end=4):
   print(message[start:end])

display_message("message", end=3)
```

This above is valid and start will have the default value of 0. A
restriction placed on this is after the first named argument then all
arguments after it must also be named. The following is not valid

``` python
display_message(end=5, start=1, "my message")
```

because the third argument (\"my message\") is an unnamed argument.

## Nested functions

Nested functions are functions defined within other functions. Arbitrary
level of nesting is possible.

Nested functions can read variables declared in the immediately outside
function. For such variables that are mutable, nested functions can even
modify them. For such variables that are immutable such as integers,
attempt at modification in the nested function throws UnboundLocalError.
In Python 3, an immutable immediately outside variable can be declared
in the nested function to be *nonlocal*, in an analogy to *global*. Once
this is done, the nested function can assign a new value to that
variable and that modification is going to be seen outside of the nested
function.

Nested functions can be used in #Closures, on
which see below. Furthermore, they can be used to reduce repetion of
code that pertains only to a single function, often with reduced
argument list owing to seeing the immediately outside variables.

An example of a nested function that modifies an immediately outside
variable that is a list and therefore mutable:

``` python
def outside():
  outsideList = [1, 2]
  def nested():
    outsideList.append(3)
  nested()
  print(outsideList)
```

An example in which the outside variable is first accessed *below* the
nested function definition and it still works:

``` python
def outside():
  def nested():
    outsideList.append(3)
  outsideList = [1, 2]
  nested()
  print(outsideList)
```

Keywords: inner functions, internal functions, local functions.

Links:

-   Nested Function in
    Python
    stackoverflow.com
-   7. Simple statements \# 7.13. The nonlocal
    statement,
    docs.python.org/3
-   Python nonlocal
    statement,
    stackoverflow.com

## Closures

A *closure* is a nested function with an after-return access to the data
of the outer function, where the nested function is returned by the
outer function as a function object. Thus, even when the outer function
has finished its execution after being called, the closure function
returned by it can refer to the values of the variables that the outer
function had when it defined the closure function.

An example:

``` python
def adder(outer_argument): # outer function
  def adder_inner(inner_argument): # inner function, nested function
    return outer_argument + inner_argument # Notice outer_argument
  return adder_inner
add5 = adder(5) # a function that adds 5 to its argument
add7 = adder(7) # a function that adds 7 to its argument
print(add5(3)) # prints 8
print(add7(3)) # prints 10
```

Closures are possible in Python because functions are *first-class
objects*. A function is merely an object of type function. Being an
object means it is possible to pass a function object (an uncalled
function) around as argument or as return value or to assign another
name to the function object. A unique feature that makes closure useful
is that the enclosed function may use the names defined in the parent
function\'s scope.

## Lambda Expressions

A lambda is an anonymous (unnamed) function. It is used primarily to
write very short functions that are a hassle to define in the normal
way. A function like this:

``` python
>>> def add(a, b):
...    return a + b
...
>>> add(4, 3)
7
```

may also be defined using lambda

``` python
>>> print ((lambda a, b: a + b)(4, 3))
7
```

Lambda is often used as an argument to other functions that expects a
function object, such as sorted()\'s \'key\' argument.

``` python
>>> sorted([[3, 4], [3, 5], [1, 2], [7, 3]], key=lambda x: x[1])
[[1, 2], [7, 3], [3, 4], [3, 5]]
```

The lambda form is often useful as a closure, such as illustrated in the
following example:

``` python
>>> def attribution(name):
...    return lambda x: x + ' -- ' + name
...
>>> pp = attribution('John')
>>> pp('Dinner is in the fridge')
'Dinner is in the fridge -- John'
```

Note that the lambda function can use the values of variables from the
scope in which it was created
(like pre and post). This is the essence of closure.

Links:

-   4.7.5. Lambda
    Expressions,
    The Python Tutorial, docs.python.org

## Generator Functions

When discussing loops, you came across the concept of an *iterator*.
This yields in turn each element of some sequence, rather than the
entire sequence at once, allowing you to deal with sequences much larger
than might be able to fit in memory at once.

You can create your own iterators, by defining what is known as a
*generator function*. To illustrate the usefulness of this, let us start
by considering a simple function to return the *concatenation* of two
lists:

``` python
def concat(a, b):
    return a + b

print(concat([5, 4, 3], ["a", "b", "c"]))
# prints [5, 4, 3, 'a', 'b', 'c']
```

Imagine wanting to do something like
`concat(list(range(0, 1000000)), list(range(1000000, 2000000)))`

That would work, but it would consume a lot of memory.

Consider an alternative definition, which takes two iterators as
arguments:

``` python
def concat(a, b):
    for i in a:
        yield i
    for i in b:
        yield i
```

Notice the use of the **yield** statement, instead of **return**. We can
now use this something like

``` python
for i in concat(range(0, 1000000), range(1000000, 2000000)):
    print(i)
```

and print out an awful lot of numbers, without using a lot of memory at
all.

Note: You can still pass a list or other sequence type wherever Python
expects an iterator (like to an argument of your `concat` function);
this will still work, and makes it easy not to have to worry about the
difference where you don't need to.

Links:

-   Functional Programming HOWTO \#
    Generators,
    docs.python.org

## External Links

-   4.6. Defining
    Functions,
    The Python Tutorial, docs.python.org

de:Python unter Linux:
Funktionen es:Inmersión
en Python/Su primer programa en Python/Declaración de
funciones
fr:Programmation_Python/Fonction
pt:Python/Conceitos
básicos/Funções
