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:

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

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:

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: 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:

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.

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.

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."

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:

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.

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:

Preventing Argument Change
If an argument is of an immutable type any changes made to it will remain local to the called function. However, if the argument is of a mutable type, such as a list, changes made to it will update the corresponding value in the calling function. Thus, if the calling function wants to make sure its mutable value passed to some unknown function will not be changed by it must create and pass a copy of the value.

An example:

Calling Functions
A function can be called by appending the arguments in parentheses to the function name or an empty pair of parentheses if the function takes no arguments.

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

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

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

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, as shown 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:

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

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:

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:

may also be defined using lambda

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

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

Note that the lambda function can use the values of variables from the scope in which it was created similar to regular locally defined functions described above. In fact, exporting the precalculations embodiedby its constructor function is one of the essential utilities of closures.

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:

Imagine wanting to do something like

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

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

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

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  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