0% found this document useful (0 votes)
1 views29 pages

Python Lab Manual 04

The document outlines the objectives and outcomes of a lab focused on functions in Python, covering topics such as creating functions, using positional and keyword arguments, and handling default parameters. It explains the syntax for defining and calling functions, the importance of the return statement, and various argument-passing techniques including tuple and dictionary packing. Additionally, it addresses common pitfalls with mutable default parameters and emphasizes the significance of return values for modifying data in the calling environment.

Uploaded by

aaimarizwan2006
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views29 pages

Python Lab Manual 04

The document outlines the objectives and outcomes of a lab focused on functions in Python, covering topics such as creating functions, using positional and keyword arguments, and handling default parameters. It explains the syntax for defining and calling functions, the importance of the return statement, and various argument-passing techniques including tuple and dictionary packing. Additionally, it addresses common pitfalls with mutable default parameters and emphasizes the significance of return values for modifying data in the calling environment.

Uploaded by

aaimarizwan2006
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 29

Lab#04 - Functions in python

Objectives:
➢ To learn and able to create function and return
➢ To learn and able to create function with positional arguments and keywords arguments
➢ To learn and able to create function with default parameters
➢ To learn and able to use argument tuple packing and unpacking
➢ To learn and able to use argument dictionary packing and unpacking

Outcomes:

Students should be able to create function and return.
➢ Students should be able to create function with positional arguments and keywords arguments
➢ Students should be able to create function with default parameters
➢ Students should be able to use argument tuple packing and unpacking
➢ Students should be able to use argument dictionary packing and unpacking

4.1 Functions
The usual syntax for defining a Python function is as follows:

The components of the definition are explained in the table below:


The final item, <statement(s)>, is called the body of the function. The body is a block of statements that
will be executed when the function is called. The body of a Python function is defined by indentation in
accordance with the off-side rule. This is the same as code blocks associated with a control structure, like
an if or while statement.

The syntax for calling a Python function is as follows:

<arguments> are the values passed into the function. They correspond to the <parameters> in the Python
function definition. You can define a function that doesn’t take any arguments, but the parentheses are still
required. Both a function definition and a function call must always include parentheses, even if they’re
empty.

As usual, you’ll start with a small example and add complexity from there. Keeping the time-honored
mathematical tradition in mind, you’ll call your first Python function f(). Here’s a script file, foo.py, that
defines and calls f():

Here’s how this code works:


• Line 1 uses the def keyword to indicate that a function is being defined. Execution of the
def statement merely creates the definition of f(). All the following lines that are indented (lines 2
to 3) become part of the body of f() and are stored as its definition, but they aren’t executed yet.

• Line 4 is a bit of whitespace between the function definition and the first line of the main
program. While it isn’t syntactically necessary, it is nice to have. To learn more about whitespace
around top-level Python function definitions, check out Writing Beautiful Pythonic Code With PEP
8.

• Line 5 is the first statement that isn’t indented because it isn’t a part of the definition of f().
It’s the start of the main program. When the main program executes, this statement is executed
first.

• Line 6 is a call to f(). Note that empty parentheses are always required in both a function
definition and a function call, even when there are no parameters or arguments. Execution proceeds
to f() and the statements in the body of f() are executed.

• Line 7 is the next line to execute once the body of f() has finished. Execution returns to this
print() statement.

The sequence of execution (or control flow) for foo.py is shown in the following diagram:

Occasionally, you may want to define an empty function that does nothing. This is referred to as a stub,
which is usually a temporary placeholder for a Python function that will be fully implemented at a later
time. Just as a block in a control structure can’t be empty, neither can the body of a function. To define a
stub function, use the pass statement:

As you can see above, a call to a stub function is syntactically valid but doesn’t do anything.

4.2 Argument Passing

So far in this tutorial, the functions you’ve defined haven’t taken any arguments. That can sometimes be
useful, and you’ll occasionally write such functions. More often, though, you’ll want to pass data into a
function so that its behavior can vary from one invocation to the next. Let’s see how to do that.

4.2.1 Positional Arguments

The most straightforward way to pass arguments to a Python function is with positional arguments (also
called required arguments). In the function definition, you specify a comma-separated list of parameters
inside the parentheses:

When the function is called, you specify a corresponding list of arguments:

The parameters (qty, item, and price) behave like variables that are defined locally to the function. When
the function is called, the arguments that are passed (6, 'bananas', and 1.74) are bound to the parameters in
order, as though by variable assignment:
In some programming texts, the parameters given in the function definition are referred to as formal
parameters, and the arguments in the function call are referred to as actual parameters:

Although positional arguments are the most straightforward way to pass data to a function, they also afford
the least flexibility. For starters, the order of the arguments in the call must match the order of the parameters
in the definition. There’s nothing to stop you from specifying positional arguments out of order, of course:

The function may even still run, as it did in the example above, but it’s very unlikely to produce the correct
results. It’s the responsibility of the programmer who defines the function to document what the appropriate
arguments should be, and it’s the responsibility of the user of the function to be aware of that information
and abide by it.

With positional arguments, the arguments in the call and the parameters in the definition must agree not
only in order but in number as well. That’s the reason positional arguments are also referred to as required
arguments. You can’t leave any out when calling the function:
Nor can you specify extra ones:

Positional arguments are conceptually straightforward to use, but they’re not very forgiving. You must
specify the same number of arguments in the function call as there are parameters in the definition, and in
exactly the same order. In the sections that follow, you’ll see some argument-passing techniques that relax
these restrictions.

4.2.2 Keyword Arguments

When you’re calling a function, you can specify arguments in the form <keyword>=<value>. In that case,
each <keyword> must match a parameter in the Python function definition. For example, the previously
defined function f() may be called with keyword arguments as follows:

Referencing a keyword that doesn’t match any of the declared parameters generates an exception:
Using keyword arguments lifts the restriction on argument order. Each keyword argument explicitly
designates a specific parameter by name, so you can specify them in any order and Python will still know
which argument goes with which parameter:

Like with positional arguments, though, the number of arguments and parameters must still match:

So, keyword arguments allow flexibility in the order that function arguments are specified, but the number
of arguments is still rigid.

You can call a function using both positional and keyword arguments:

When positional and keyword arguments are both present, all the positional arguments must come first:
Once you’ve specified a keyword argument, there can’t be any positional arguments to the right of it.

4.2.3 Default Parameters

If a parameter specified in a Python function definition has the form <name>=<value>, then <value>
becomes a default value for that parameter. Parameters defined this way are referred to as default or
optional parameters. An example of a function definition with default parameters is shown below:

When this version of f() is called, any argument that’s left out assumes its default value:

4.2.4 Mutable Default Parameter Values

Things can get weird if you specify a default parameter value that is a mutable object. Consider this Python
function definition:
f() takes a single list parameter, appends the string '###' to the end of the list, and returns the result:

The default value for parameter my_list is the empty list, so if f() is called without any arguments, then the
return value is a list with the single element '###':

Everything makes sense so far. Now, what would you expect to happen if f() is called without any
parameters a second and a third time? Let’s see:

Oops! You might have expected each subsequent call to also return the singleton list ['###'], just like the
first. Instead, the return value keeps growing. What happened?

In Python, default parameter values are defined only once when the function is defined (that is, when the
def statement is executed). The default value isn’t re-defined each time the function is called. Thus, each
time you call f() without a parameter, you’re performing .append() on the same list.

You can demonstrate this with id():


The object identifier displayed confirms that, when my_list is allowed to default, the value is the same
object with each call. Since lists are mutable, each subsequent .append() call causes the list to get longer.
This is a common and pretty well-documented pitfall when you’re using a mutable object as a parameter’s
default value. It potentially leads to confusing code behavior, and is probably best avoided.

As a workaround, consider using a default argument value that signals no argument has been specified.
Most any value would work, but None is a common choice. When the sentinel value indicates no argument
is given, create a new empty list inside the function:

4.3 The return Statement

What’s a Python function to do then? After all, in many cases, if a function doesn’t cause some change in
the calling environment, then there isn’t much point in calling it at all. How should a function affect its
caller?

Well, one possibility is to use function return values. A return statement in a Python function serves two
purposes:

It immediately terminates the function and passes execution control back to the caller.

It provides a mechanism by which the function can pass data back to the caller.
4.3.1 Exiting a Function

Within a function, a return statement causes immediate exit from the Python function and transfer of
execution back to the caller:

In this example, the return statement is actually superfluous. A function will return to the caller when it
falls off the end—that is, after the last statement of the function body is executed. So, this function would
behave identically without the return statement.

However, return statements don’t need to be at the end of a function. They can appear anywhere in a
function body, and even multiple times. Consider this example:

The first two calls to f() don’t cause any output, because a return statement is executed and the function
exits prematurely, before the print() statement on line 6 is reached.

This sort of paradigm can be useful for error checking in a function. You can check several error conditions
at the start of the function, with return statements that bail out if there’s a problem:
If none of the error conditions are encountered, then the function can proceed with its normal processing.

4.3.2 Returning Data to the Caller

In addition to exiting a function, the return statement is also used to pass data back to the caller. If a return
statement inside a Python function is followed by an expression, then in the calling environment, the
function call evaluates to the value of that expression:

Here, the value of the expression f() on line 5 is 'foo', which is subsequently assigned to variable s.

A function can return any type of object. In Python, that means pretty much anything whatsoever. In the
calling environment, the function call can be used syntactically in any way that makes sense for the type of
object the function returns.

For example, in this code, f() returns a dictionary. In the calling environment then, the expression f()
represents a dictionary, and f()['baz'] is a valid key reference into that dictionary:

In the next example, f() returns a string that you can slice like any other string:
Here, f() returns a list that can be indexed or sliced:

If multiple comma-separated expressions are specified in a return statement, then they’re packed and
returned as a tuple:

When no return value is given, a Python function returns the special Python value None:

The same thing happens if the function body doesn’t contain a return statement at all and the function falls
off the end:
Recall that None is falsy when evaluated in a Boolean context.

Since functions that exit through a bare return statement or fall off the end return None, a call to such a
function can be used in a Boolean context:

Here, calls to both f() and g() are falsy, so f() or g() is as well, and the else clause executes.

4.3.4 Revisiting Side Effects

Suppose you want to write a function that takes an integer argument and doubles it. That is, you want to
pass an integer variable to the function, and when the function returns, the value of the variable in the calling
environment should be twice what it was.

As you now know, Python integers are immutable, so a Python function can’t change an integer argument
by side effect:

However, you can use a return value to obtain a similar effect. Simply write double() so that it takes an
integer argument, doubles it, and returns the doubled value. Then, the caller is responsible for the
assignment that modifies the original value:
This is arguably preferable to modifying by side effect. It’s very clear that x is being modified in the calling
environment because the caller is doing so itself. Anyway, it’s the only option, because modification by
side effect doesn’t work in this case.

Still, even in cases where it’s possible to modify an argument by side effect, using a return value may still
be clearer. Suppose you want to double every item in a list. Because lists are mutable, you could define a
Python function that modifies the list in place:

Unlike double() in the previous example, double_list() actually works as intended. If the documentation for
the function clearly states that the list argument’s contents are changed, then this may be a reasonable
implementation.

However, you can also write double_list() to pass the desired list back by return value and allow the caller
to make the assignment, similar to how double() was re-written in the previous example:

Either approach works equally well. As is often the case, this is a matter of style, and personal preferences
vary. Side effects aren’t necessarily consummate evil, and they have their place, but because virtually
anything can be returned from a function, the same thing can usually be accomplished through return values
as well.
4.4 Variable-Length Argument Lists

In some cases, when you’re defining a function, you may not know beforehand how many arguments you’ll
want it to take. Suppose, for example, that you want to write a Python function that computes the average
of several values. You could start with something like this:

All is well if you want to average three values:

However, as you’ve already seen, when positional arguments are used, the number of arguments passed
must agree with the number of parameters declared. Clearly then, all isn’t well with this implementation of
avg() for any number of values other than three:

You could try to define avg() with optional parameters:

This allows for a variable number of arguments to be specified. The following calls are at least syntactically
correct:
But this approach still suffers from a couple of problems. For starters, it still only allows up to five
arguments, not an arbitrary number. Worse yet, there’s no way to distinguish between the arguments that
were specified and those that were allowed to default. The function has no way to know how many
arguments were actually passed, so it doesn’t know what to divide by:

Evidently, this won’t do either. You could write avg() to take a single list argument:

At least this works. It allows an arbitrary number of values and produces a correct result. As an added
bonus, it works when the argument is a tuple as well:

The drawback is that the added step of having to group the values into a list or tuple is probably not
something the user of the function would expect, and it isn’t very elegant. Whenever you find Python code
that looks inelegant, there’s probably a better option.

In this case, indeed there is! Python provides a way to pass a function a variable number of arguments with
argument tuple packing and unpacking using the asterisk (*) operator.

4.4.1 Argument Tuple Packing


When a parameter name in a Python function definition is preceded by an asterisk (*), it indicates argument
tuple packing. Any corresponding arguments in the function call are packed into a tuple that the function
can refer to by the given parameter name. Here’s an example:

In the definition of f(), the parameter specification *args indicates tuple packing. In each call to f(), the
arguments are packed into a tuple that the function can refer to by the name args. Any name can be used,
but args is so commonly chosen that it’s practically a standard.

Using tuple packing, you can clean up avg() like this:

Better still, you can tidy it up even further by replacing the for loop with the built-in Python function sum(),
which sums the numeric values in any iterable:

Now, avg() is concisely written and works as intended.

Still, depending on how this code will be used, there may still be work to do. As written, avg() will produce
a TypeError exception if any arguments are non-numeric:
To be as robust as possible, you should add code to check that the arguments are of the proper type. Later
in this tutorial series, you’ll learn how to catch exceptions like TypeError and handle them appropriately.

5.4.2 Argument Tuple Unpacking

An analogous operation is available on the other side of the equation in a Python function call. When an
argument in a function call is preceded by an asterisk (*), it indicates that the argument is a tuple that should
be unpacked and passed to the function as separate values:

In this example, *t in the function call indicates that t is a tuple that should be unpacked. The unpacked
values 'foo', 'bar', and 'baz' are assigned to the parameters x, y, and z, respectively.

Although this type of unpacking is called tuple unpacking, it doesn’t only work with tuples. The asterisk
(*) operator can be applied to any iterable in a Python function call. For example, a list or set can be
unpacked as well:
You can even use tuple packing and unpacking at the same time:

Here, f(*a) indicates that list a should be unpacked and the items passed to f() as individual values. The
parameter specification *args causes the values to be packed back up into the tuple args.

4.4.3 Argument Dictionary Packing

Python has a similar operator, the double asterisk (**), which can be used with Python function parameters
and arguments to specify dictionary packing and unpacking. Preceding a parameter in a Python function
definition by a double asterisk (**) indicates that the corresponding arguments, which are expected to be
key=value pairs, should be packed into a dictionary:
In this case, the arguments foo=1, bar=2, and baz=3 are packed into a dictionary that the function can
reference by the name kwargs. Again, any name can be used, but the peculiar kwargs (which is short for
keyword args) is nearly standard. You don’t have to adhere to it, but if you do, then anyone familiar with
Python coding conventions will know straightaway what you mean.

4.4.4 Argument Dictionary Unpacking

Argument dictionary unpacking is analogous to argument tuple unpacking. When the double asterisk (**)
precedes an argument in a Python function call, it specifies that the argument is a dictionary that should be
unpacked, with the resulting items passed to the function as keyword arguments:

The items in the dictionary d are unpacked and passed to f() as keyword arguments. So, f(**d) is equivalent
to f(a='foo', b=25, c='qux'):
In fact, check this out:

Here, dict(a='foo', b=25, c='qux') creates a dictionary from the specified key/value pairs. Then, the double
asterisk operator (**) unpacks it and passes the keywords to f().

4.4.5 Putting It All Together

Think of *args as a variable-length positional argument list, and **kwargs as a variable-length keyword
argument list.

All three—standard positional parameters, *args, and **kwargs—can be used in one Python function
definition. If so, then they should be specified in that order:

This provides just about as much flexibility as you could ever need in a function interface!

4.4.6 Multiple Unpacking’s in a Python Function Call

Python version 3.5 introduced support for additional unpacking generalizations, as outlined in PEP 448.
One thing these enhancements allow is multiple unpacking’s in a single Python function call:
You can specify multiple dictionary unpackings in a Python function call as well:

By the way, the unpacking operators * and ** don’t apply only to variables, as in the examples above. You
can also use them with literals that are iterable:
Here, the literal lists [1, 2, 3] and [4, 5, 6] are specified for tuple unpacking, and the literal dictionaries {'a':
1, 'b': 2} and {'x': 3, 'y': 4} are specified for dictionary unpacking.

4.5 Docstrings

When the first statement in the body of a Python function is a string literal, it’s known as the function’s
docstring. A docstring is used to supply documentation for a function. It can contain the function’s purpose,
what arguments it takes, information about return values, or any other information you think would be
useful.

The following is an example of a function definition with a docstring:

Technically, docstrings can use any of Python’s quoting mechanisms, but the recommended convention is
to triple-quote using double-quote characters ("""), as shown above. If the docstring fits on one line, then
the closing quotes should be on the same line as the opening quotes.
Multi-line docstrings are used for lengthier documentation. A multi-line docstring should consist of a
summary line, followed by a blank line, followed by a more detailed description. The closing quotes should
be on a line by themselves:

Docstring formatting and semantic conventions are detailed in PEP 257.

When a docstring is defined, the Python interpreter assigns it to a special attribute of the function called
__doc__. This attribute is one of a set of specialized identifiers in Python that are sometimes called magic
attributes or magic methods because they provide special language functionality.

You can access a function’s docstring with the expression <function_name>.__doc__. The docstrings for
the above examples can be displayed as follows:

In the interactive Python interpreter, you can type help(<function_name>) to display the docstring for
<function_name>:

4.6 lambda function

A lambda function is a small anonymous function.

A lambda function can take any number of arguments, but can only have one expression.
The identity function, a function that returns its argument, is expressed with a standard Python function
definition using the keyword def as follows:

identity() takes an argument x and returns it upon invocation.

In contrast, if you use a Python lambda construction, you get the following:

In the example above, the expression is composed of:

• The keyword: lambda


• A bound variable: x
• A body: x

You can write a slightly more elaborated example, a function that adds 1 to an argument, as follows:

You can apply the function above to an argument by surrounding the function and its argument with
parentheses:

Because a lambda function is an expression, it can be named. Therefore you could write the previous code
as follows:
The above lambda function is equivalent to writing this:

These functions all take a single argument. You may have noticed that, in the definition of the lambdas, the
arguments don’t have parentheses around them. Multi-argument functions (functions that take more than
one argument) are expressed in Python lambdas by listing arguments and separating them with a comma
(,) but without surrounding them with parentheses:

The lambda function assigned to full_name takes two arguments and returns a string interpolating the two
parameters first and last. As expected, the definition of the lambda lists the arguments with no parentheses,
whereas calling the function is done exactly like a normal Python function, with parentheses surrounding
the arguments.

4.7 Built-in Function


4.7.1 map()

map() loops over the items of an input iterable (or iterables) and returns an iterator that results from applying
a transformation function to every item in the original input iterable.

According to the documentation, map() takes a function object and an iterable (or multiple iterables) as
arguments and returns an iterator that yields transformed items on demand. The function’s signature is
defined as follows:
map() applies function to each item in iterable in a loop and returns a new iterator that yields transformed
items on demand. function can be any Python function that takes a number of arguments equal to the number
of iterables you pass to map().

This first argument to map() is a transformation function. In other words, it’s the function that transforms
each original item into a new (transformed) item.

The operation that map() performs is commonly known as a mapping because it maps every item in an
input iterable to a new item in a resulting iterable. To do that, map() applies a transformation function to all
the items in the input iterable.

To better understand map(), suppose you need to take a list of numeric values and transform it into a list
containing the square value of every number in the original list. In this case, you can use a for loop and
code something like this:

When you run this loop on numbers, you get a list of square values. The for loop iterates over numbers and
applies a power operation on each value. Finally, it stores the resulting values in squared.

You can achieve the same result without using an explicit loop by using map(). Take a look at the following
reimplementation of the above example:

square() is a transformation function that maps a number to its square value. The call to map() applies
square() to all of the values in numbers and returns an iterator that yields square values. Then you call list()
on map() to create a list object containing the square values.

Since map() is written in C and is highly optimized, its internal implied loop can be more efficient than a
regular Python for loop. This is one advantage of using map().
A second advantage of using map() is related to memory consumption. With a for loop, you need to store
the whole list in your system’s memory. With map(), you get items on demand, and only one item is in
your system’s memory at a given time.

You might also like