CIS 1051 - Temple Rome Spring 2024¶

Intro to Problem solving and¶

Programming in Python¶

LOGO

Functions¶

Prof. Dario Abbondanza

( dario.abbondanza@temple.edu )

In the context of programming, a function is a named sequence of statements that performs a computation.

To define a function, you specify:

  • the name
  • the sequence of statements

Later, you can “call” the function by name.

This is similar to what we said for values and variables.

Function Calls¶

... we have already seen many examples of function calls:

In [1]:
type(42)
Out[1]:
int

The name of the function is type.

The expression in parentheses is called the argument of the function (aka input).

The result (aka output) of a function is also called the return value.

It is common to say that a function “takes” an argument and “returns” a result.

Python provides functions that convert values from one type to another.

The int function takes any value and converts it to an integer, if it can

In [4]:
int('32')
Out[4]:
32

... or complains otherwise

In [3]:
int('Hello')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-3-3f8bc6d8fcab> in <module>()
----> 1 int('Hello')

ValueError: invalid literal for int() with base 10: 'Hello'

int

can convert floating-point values and strings to integers, but it doesn’t round off; it chops off the fraction part:

In [12]:
int(3.99999)
Out[12]:
3
In [13]:
int(-2.3)
Out[13]:
-2

float

converts integers and strings to floating-point numbers:

In [14]:
float(32)
Out[14]:
32.0
In [15]:
float('3.14159')
Out[15]:
3.14159

str

converts its argument to a string:

In [16]:
str(32)
Out[16]:
'32'
In [17]:
str(3.14159)
Out[17]:
'3.14159'

Math Functions¶

Python has a math module that provides most of the familiar mathematical functions.

A module is a file that contains a collection of related functions.

Before we can use the functions in a module, we have to import it with an import statement:

In [18]:
import math

... under the hood, this creates a module object named math.

If you display it, you get some information:

In [20]:
math
Out[20]:
<module 'math' (built-in)>

The module object contains the functions and variables defined in the module.

To access one of the functions, you have to specify the name of the module and the name of the function, separated by a dot (aka period).

This format is called dot notation.

In [32]:
signal_power = 350; noise_power = 10
ratio = signal_power / noise_power
decibels = 10 * math.log10(ratio)
print(str(decibels) + " dB ")

radians = 0.7
height = math.sin(radians)
15.440680443502757 dB 

here we used log10 to compute a signal-to-noise ratio in decibels

math module also provides log, which computes logarithms base e

In [33]:
degrees = 45
radians = degrees / 180.0 * math.pi
math.sin(radians)
Out[33]:
0.7071067811865475

here, the name of the variable is a hint that, as other trigonometric functions, it only takes arguments in radians!

The expression math.pi gets the variable pi from the math module, or better: a floating-point approximation of its value!

If you know trigonometry, you can check the previous result by comparing it to half the square root of 2:

In [35]:
math.sqrt(2) / 2.0
Out[35]:
0.7071067811865476

Composition¶

One of the most useful features of programming languages is their ability to compose small building blocks.

The argument of a function can be any kind of expression, including arithmetic operators:

In [38]:
x = math.sin(degrees / 360.0 * 2 * math.pi)
print(x)
0.7071067811865475

... even function calls:

In [40]:
x = math.exp(math.log(x+1))
print(x)
2.7071067811865475

Almost anywhere you can put a value, you can put an arbitrary expression, with one exception:

the left side of an assignment statement has to be a variable name.

Any other expression on the left side is a syntax error.

(we will see exceptions to this rule later)

In [44]:
hours = 3
minutes = hours * 60                 # right
In [45]:
hours * 60 = minutes                 # wrong!
  File "<ipython-input-45-2be1c89a3aa6>", line 1
    hours * 60 = minutes                 # wrong!
                                                 ^
SyntaxError: can't assign to operator

Defining New Functions¶

We have only seen functions that come with Python, but it is also possible to add new functions.

Specifying:

  • the name of a new function
  • the sequence of statements to execute, when the function is called
In [46]:
def print_lyrics():
    print("I'm a lumberjack, and I'm okay.")
    print("I sleep all night and I work all day.")

def is a keyword that indicates this is a function definition.

  • The name of the function is print_lyrics.

The rules for function names are the same as for variable names.

... avoid having a variable and a function with the same name!

  • The empty parentheses after the name indicate that this function doesn’t take any arguments.
  • The first line of the function definition is called the header.
  • The rest is called the body.

The header has to end with a colon and the body has to be indented.

By convention, indentation is always four spaces.

The body can contain any number of statements.

The strings in the print statements are enclosed in double quotes.

Single quotes and double quotes do the same thing, except when a single quote (an apostrophe) appears within the string, as above.

All quotation marks (single and double) must be straight quotes. “Curly quotes”, like the ones in this sentence, are not legal in Python.

Typing a function definition in interactive mode, the interpreter prints dots (...) to let you know the definition is uncomplete:

>>> def print_lyrics():
...     print("I'm a lumberjack, and I'm okay.")
...     print("I sleep all night and I work all day.")
...

To end the function, enter an empty line!

Defining a function creates a function object, which has type function:

In [49]:
print(print_lyrics)
<function print_lyrics at 0x7efc24806ae8>
In [50]:
type(print_lyrics)
Out[50]:
function

Syntax for calling custom function is the same as for built-in:

In [51]:
print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Once you define a function, you can use it inside another function:

In [52]:
def repeat_lyrics():
    print_lyrics()
    print_lyrics()
In [53]:
 repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Definitions and Uses¶

... the code fragments from the previous section, all together, looks like this:

In [54]:
def print_lyrics():
    print("I'm a lumberjack, and I'm okay.")
    print("I sleep all night and I work all day.")

def repeat_lyrics():
    print_lyrics()
    print_lyrics()

repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

This program contains: two function definitions and a function call

  • definitions get executed just like other statements, but generate no output.
  • statements inside it do not run until the function is called
  • the effect is to create a function object: you have to, before you can run it.

In other words: the function definition has to run before the function gets called!

Flow of Execution¶

To ensure that a function is defined before its first use, you have to know the order statements run in (aka flow of execution).

  • Execution always begins at the first statement of the program.
  • Statements are run one at a time, in order from top to bottom.

Function definitions do not alter the flow of execution of the program: remember that statements inside the function don’t run until the function is called!

A function call is like a detour in the flow of execution.

Instead of going to the next statement, the flow:

  • jumps to the body of the function
  • runs the statements there
  • then comes back to pick up where it left off

... simple enough, until you remember that one function can call another.

While in the middle of a function...

the program might have to call (and run the statements in) another function

... and there

it might have to run yet another function!

Thus, when you read a program, you don’t always want to read from top to bottom.

... it makes more sense to follow the flow of execution!

Parameters and Arguments¶

Some functions require an argument, some others take more than one argument:

  • math.sin a number
  • math.pow the base and the exponent

Inside the function, the arguments are assigned to **local** variables called **parameters**.

In [57]:
def print_twice(bruce):
    print(bruce)
    print(bruce)
  • this function assigns the argument to a parameter named bruce
  • when is called, it prints the value of the parameter twice

This function works with any value that can be printed.

In [59]:
print_twice('Spam')
Spam
Spam
In [60]:
print_twice(42)
42
42
In [62]:
print_twice(math.pi)
3.141592653589793
3.141592653589793

Same rules of **composition** that apply to built-in functions apply to custom functions too.

In [63]:
print_twice(math.cos(math.pi))
-1.0
-1.0

Arguments are evaluated before the function is called. You can also pass a variable as argument.

In [64]:
michael = 'Eric, the half a bee.'
print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.

The name of the variable we pass as argument (michael) has nothing to do with the name of the parameter (bruce).

It doesn’t matter what the value was called **back home** (in the caller); here in print_twice, **we call everybody bruce**.

Variables and Parameters are Local¶

When you create a variable inside a function, it is local, which means that it only exists inside the function.

In [66]:
def cat_twice(part1, part2):
    cat = part1 + part2
    print_twice(cat)

This function:

  • takes two arguments
  • concatenates them
  • prints the result twice
In [66]:
def cat_twice(part1, part2):
    cat = part1 + part2
    print_twice(cat)
In [67]:
line1 = 'Bing tiddle '
In [68]:
line2 = 'tiddle bang.'
In [70]:
cat_twice(line1, line2)
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.

When cat_twice terminates, the variable cat is destroyed.

Trying to print it, we get an exception:

In [71]:
print(cat)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-71-7c3364e51d98> in <module>()
----> 1 print(cat)

NameError: name 'cat' is not defined

Parameters are also local: outside print_twice ... nobody knows bruce

In [72]:
print(bruce)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-72-f7a937b82c5b> in <module>()
----> 1 print(bruce)

NameError: name 'bruce' is not defined

Functions and Void Functions¶

Some of the functions we have used, return results; other, perform an action but don’t return a value.

They are called void functions.

When your function returns a value, you usually want to do something with it:

  • assign it to a variable
  • use it within an expression
In [81]:
x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2

When you call a function in interactive mode, Python displays the result:

>>> math.sqrt(5)
2.2360679774997898

But in a script, if you call a function all by itself, the return value is lost forever!

math.sqrt(5)

Contrary, void functions might:

  • display something on the screen
  • have some other effect

They don’t have a return value: if you assign the result to a variable, you get a special value called None.

In [87]:
result = print_twice('Bing')

print(result)
Bing
Bing
None

It is a special value that has its own type.

In [88]:
print(type(result))
<class 'NoneType'>

Why Functions?¶

... why it is worth the trouble to divide a program into functions. There are several reasons:

  • name a group of statements, which makes a complex program easier to read and debug (test the parts one at a time).
  • make a program smaller, eliminating repetitive code (make changes in one place), well-designed functions are then reusable.

Debugging¶

One of the most important skills you will acquire is debugging.

Although it can be frustrating, debugging is one of the most intellectually rich, challenging, and interesting parts of programming.

It is like detective work: you are confronted with clues and you have to infer the processes and events that led to the results you see.

It is also like an experimental science:

  • Once you have an idea about what is going wrong, you modify your program and try again.
  • If your hypothesis was correct, you take a step closer to a working program.
  • If your hypothesis was wrong, you have to come up with a new one.