Lec04-Functions (New)
Lec04-Functions (New)
Computer
Principles and
Python
Programming
Lecture 4: Functions
2024-25 Term 2
By Dr. King Tin Lam
Outline
• Defining functions
• The return statement
• Parameter passing
• Positional arguments
• Keyword arguments
• Arbitrary arguments
• Function annotations
• Inner functions
• Lambda expressions (preview)
• Variable scope and lifetime
2
Why do we use functions in programming?
• Make an application or system modular!
• Modular programming - separating the functionality of a program
into independent code modules; functions can be seen a kind of such
modules
• Advantages:
• Code reusability
• Easier to debug or maintain (extend) the program
• Facilitate software testing
• Code optimization: write less code
fun1()
fun2() program
3
script script (modular)
Why Do We Use Functions in Programming?
• So far, we write program code as sequential a, b = 0, 1
while a < 100:
lines in the body of the main module. print(a, end=' ')
a, b = b, a+b
• Suppose you need to print three Fibonacci print()
sequences from 0 to 100, from 0 to 500, and a, b = 0, 1
from 0 to 2000, would you write the code on while a < 500:
the right? print(a, end=' ')
a, b = b, a+b
• It looks very redundant and clunky! print()
a, b = 0, 1
• A better solution is to move your code into a while a < 2000:
function and generalize it for future reuse. print(a, end=' ')
a, b = b, a+b
print()
4
Why Do We Use Functions in Programming?
parameter
• Write once: def fib(n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
argument
a, b = b, a+b
print()
…
5
Types of Functions
• Built-in functions - Functions that are built into Python.
• User-defined functions - Functions defined by the programmer.
6
Built-in Functions
• The Python interpreter has a number of functions and types built into it that are
always available. They are listed here in alphabetical order.
abs() delattr() hash() memoryview() set()
all() dict() help() min() setattr()
any() dir() hex() next() slice()
ascii() divmod() id() object() sorted()
bin() enumerate() input() oct() staticmethod()
bool() eval() int() open() str()
breakpoint() exec() isinstance() ord() sum()
bytearray() filter() issubclass() pow() super()
bytes() float() iter() print() tuple()
callable() format() len() property() type()
chr() frozenset() list() range() vars()
classmethod() getattr() locals() repr() zip()
compile() globals() map() reversed() __import__()
complex() hasattr() max() round() 7
Example: The round() Function
have two parameters
• round(number[, ndigits])
• Return number rounded to ndigits precision after the decimal
point. If ndigits is omitted or is None, it returns the nearest integer
to its input.
>>> round(3.4) >>> round(2.4)
3 2
>>> round(3.5) >>> round(2.5)
4 2 Not 3 but 2!
• The last result is not a bug but a "new norm" (a change since Python
3) which rounds half-way cases to the nearest even number. This is
so-called "round half to even" or "banker's rounding".
8
Detour
• Check the doc: This is not a bug: it’s a result of the fact that most
decimal fractions can’t be represented exactly as a float.
9
Detour
10
Defining Functions
• A function is a self-contained block of one or more statements that
performs a special task when called.
• Syntax: def name_of_function(parameters): function header
statement1
statement2
statement3 function body
...
statementN
11
Defining a Function
• Example:
def fib(n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
12
Control Flow Across Function Calls
print("At the beginning ...")
• You can assign the function object to another variable and call it:
>>> f = fib
>>> f
<function fib at 0x7f91de268e50>
>>> f(2000)
>>> 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
14
Defining Functions
• The last example is a void function, which doesn't return a value.
• You can define a function that returns a value.
def fib2(n):
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
Defining Functions
• The statement result.append(a) calls a method of the list object
result. A method is a function that belongs to an object and is called
via the syntax obj.methodname, where obj is an object (reference),
and methodname is the name of a method defined by the object’s
type. More details will be given when teaching OOP.
def fib2(n):
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
16
The return Statement
• The return statement is used to exit a function and go back to the
place from where it was called.
• Syntax: return [expression_list]
They are the same; no behavioral difference; all return None to the caller.
17
The return Statement Indeed, there is a built-in function
also called abs() in Python. We
wrote this function just as an example
of returning a conditional expression.
• Example: returning an expression
def abs(num):
"""Return the absolute value of a number."""
print("absolute value")
return num if num >= 0 else -num
x = abs(-1)
print(x)
print(abs(2.1)) absolute
1
Output: absolute
2.1
18
Returning Multiple Values
• It is also possible for a function to return multiple values and assign
the returned multiple values to multiple variables.
• Example:
def search(lst, target):
for index, val in enumerate(lst, start=1):
if val == target:
return index, val
return None, "Not found"
20
Multiple Return Statements
• How about this version?
def is_divisible(a, b):
if not a % b:
return True
else:
return False You will never reach this
return None statement – pointless to
put it here.
• The correct and concise version:
def is_divisible(a, b):
if not a % b:
return not a%b
return True or
return False return a%b == 0
21
Default Argument Values
• You can specify a default value for one or more arguments.
• Example:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt + ' ')
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
https://docs.python.org/3/tutorial/controlflow.html#default-argument-values 22
Default Argument Values
• The function can be called in several ways:
1. Giving only the mandatory argument:
ans = ask_ok('Are you sure to quit?')
23
Default Argument Values
• Sample outputs: >>> ans = ask_ok('Are you sure to quit?', 2)
Are you sure to quit? I
Please try again!
Are you sure to quit? am
Please try again!
Are you sure to quit? stupid
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in ask_ok
ValueError: invalid user response
def func(arg=default):
print(arg)
default = 3 Output:
func() 2
• Note: default values are evaluated only once at the point of function
definition in the defining scope.
25
Default Argument Values
• Compare outputs of the following code segments?
def f(a, L=[]): def f(a, L=None):
L.append(a) if L is None:
return L L = [] L is set to None when the function
L.append(a) definition time
print(f(1)) [1] return L
Output:
print(f(2)) [1, 2]
print(f(3)) [1, 2, 3] 2
print(f(1)) [1]
print(f(2)) [2]
print(f(3)) [3]
• Since default values are evaluated only once, this makes a difference
when the default is a mutable object such as a list, dictionary, or
instances of most classes.
26
Default Argument Values
• Try this out:
def display(code=123, mesg):
pass
27
Keyword Arguments
• Revisit the built-in print() function that you use often!
• In fact, it accepts these optional attributes sep, end, file, and flush
as keyword arguments.
• Examples:
>>> print('he', 'she', 'it')
he she it
>>> print('he', 'she', 'it', sep=', ')
he, she, it
28
Keyword Arguments
• Functions can also be called using keyword arguments.
• Syntax to call a function using keyword argument is:
function_name(pos_args, keyword1=value, keyword2=value2, …)
29
Precautions of Using Keyword Arguments
• For this function: def display(name, action="greet", mesg="Hello,"):
if action == "greet":
print(mesg, name)
elif action == "punch":
print("Take this!", name)
30
Precautions of Using Keyword Arguments
• For this function: def display(name, action="greet", mesg="Hello,"):
...
32
Arbitrary Argument Lists
list[1,2,3]
tuple(1,2,3)
• A function in Python can also be defined to receive an arbitrary
number of arguments, which are wrapped up in a tuple (a read-only
list of items) when being passed from the caller.
• Syntax: def func(*args):
• Zero or more normal arguments can be defined before the variable number
of arguments.
• Normally, these variadic arguments will be last in the list of formal
parameters, because they scoop up all remaining input arguments passed to
the function. Any formal parameters which occur after the *args parameter
are keyword-only arguments.
33
Arbitrary Argument Lists
Syntax: str_name.join(iterable)
• Examples: The join() method returns a string concatenating
items of the iterable with str_name as separator.
34
Unpacking Argument Lists
• In some reverse situation, if the actual arguments are already in a list
or tuple but your function expects separate positional arguments,
then you need to "unpack" the list or tuple first using the * operator.
• Examples: def sum_of_4(a, b, c, d):
return a + b + c + d
>>> x = [1, 2, 3, 4]
35
Function Annotations (Since Python 3.5)
• Function annotations are optional metadata information about the
types used by user-defined functions (see PEP 3107 and PEP 484).
• Annotations are stored in the __annotations__ attribute of the
function as a dictionary and have no effect on any other part of the
function.
• Parameter annotations are defined by a colon after the parameter
name, followed by an expression evaluating to the value of the
annotation.
• Return annotations are defined by a literal ->, followed by an
expression, between the parameter list and the colon denoting the
end of the def statement.
36
Function Annotations default argument
Incorrect syntax: def movie_info(title: str, year: int, rating = 3.5 : float) -> str:
Putting the default argument before the parameter annotation is incorrect.
37
(Optional)
Function Annotations
• Note that Python doesn’t provide any semantics with annotations. It
only provides nice syntactic support for associating metadata.
• Typing is not necessarily the only one purpose of adding annotations.
• So, you may also write annotations like below (but the type checkers
in some IDEs may highlight them even though the code can run):
def movie_info(t: 'movie title (str)', y: 'release year (int)',
rating: 'between 0 and 5 (float)' = 3.5) -> 'formatted string':
print("Annotations:", movie_info.__annotations__)
return f"'{t}' released in {y} is rated {rating:.1f} stars."
Type Hints
• The typing module provides runtime support for type hints.
• The most fundamental support consists of the types:
• Any - Special type indicating an unconstrained type.
• Union - Union type; Union[X, Y], equiv. X | Y, means either X or Y.
• Callable - Callable type
• TypeVar - Type variable
• Generic - Abstract base class for generic types
• For a full specification, please see PEP 484.
• For a simplified introduction to type hints, see PEP 483.
39
(Optional)
Type Hints
• For example, to annotate a function f that accepts an integer and
returns either a list of integers or a string, write: return value is either list of int or str
def f(n: int) -> list[int] | str: (PEP 604, since 3.10)
or
from typing import Union
40
Unix Philosophy: Do one thing and do it
well!
• What do you think about this function?
def print_twins(start, end):
assert start > 1
for n in range(start, end-1):
is_prime1 = True
for i in range(2, n):
if n % i == 0: These two parts look redundant.
is_prime1 = False
if is_prime1:
is_prime2 = True Implication:
for i in range(2, n+2): This function can be decomposed further.
if (n + 2) % i == 0:
is_prime2 = False
if (is_prime1 and is_prime2):
print(f"({n}, {n+2})") 41
Unix Philosophy: Do one thing and do it
well!
• This one looks much better – every function does one thing only.
• Better chance for code reuse
def is_prime(n):
for i in range(2, n):
if n % i == 0:
return False
return True
print_twins(2, 100) 43
Lambda Expressions
• When we create a function in Python, we use the def keyword and
give the function a name.
• Lambda expressions are used to create anonymous functions (i.e.,
those without a name), allowing us to define functions more quickly.
• A lambda expression yields a function object.
• Syntax:
def func_obj(arguments):
func_obj = lambda arguments: expression
return expression
• Examples:
def add(x, y):
add = lambda x, y: x + y
return x + y
44
Lambda Expressions
• Lambdas differ from normal Python functions because they can have
only one expression, can't contain any statements and their return
type is a function object.
lambda x, y: x + y
• So, the line of code above doesn't exactly return the value x + y but
the function that calculates x + y.
45
Lambda Expressions
• Examples: # define a usual function
def cube(y):
return y*y*y
46
Scope and Lifetime of Variables
47
Scope and Lifetime of Variables
• A variable is only available from inside the region it is created. This is
called scope.
def my_func():
x = 123 x is only inside the function
print(x) 123
my_func()
print(x) NameError: name 'x' is not defined
48
Scope and Lifetime of Variables
• Global Scope - a variable created in the main body of the Python
code is a global variable and belongs to the global scope.
• Global variables are available from within any scope, global and local.
x = 123
def my_func():
print(x)
my_func() 123
print(x) 123
49
Scope and Lifetime of Variables
• Let's consider the case with an inner function!
• The variable x is not available outside my_func(), but it is available for
any function inside its body.
def my_func():
x = 246
def my_inner_func():
print(x) 246
my_inner_func()
my_func()
50
Scope and Lifetime of Variables
• How about this code? What is the output?
local variable, just use it inside the function
def my_func(lst):
for i, val in enumerate(lst, 1): 1 a
print(i, val) 2 b
3 c
print("# total items =", i) 4 d
print(val) 5 e
# total items = 5
my_func(["a", "b", "c", "d", "e"]) e
• No error above! Python does not have block scopes as C/C++ do.
• So, variables i and val are visible for the rest of the function body once
they are created (even created in the for loop).
51
Scope and Lifetime of Variables
• But soon later, you will learn a technique called list comprehension:
def my_func(lst):
tmp = [val for val in lst]
print(val)
NameError: name 'val' is not defined
• In this case (and in Python 3), the variable val is not visible outside
the brackets.
• The same principle applies to comprehensions of other types such as
dict and set.
52
Scope and Lifetime of Variables
• How about this code? x = 2
my_func() 3
print(x) 2
• If you operate with the same variable name inside and outside of a
function, Python will treat them as two separate variables, one in the
global scope and one in the local scope.
• This is an example of variable shadowing (or name masking).
53
Scope and Lifetime of Variables
• Another example (nested):
x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)
inner()
print("outer:", x)
inner: 2
outer() outer: 1
print("global:", x) global: 0
54
Scope and Lifetime of Variables
• As there is no variable x = 0
declaration but only variable def outer():
assignment in Python, the x = 1
keyword nonlocal introduced
def inner():
in Python 3 is used to avoid nonlocal x
variable shadowing and x = 2
assign to non-local variables. print("inner:", x) inner: 2
outer: 2
inner() global: 0
print("outer:", x)
outer()
print("global:", x)
55
Scope and Lifetime of Variables
If you add nonlocal keyword
x = 0 to the x here, you will get
• In this example, accessing the SyntaxError: no binding for
def outer():
variable x defined in outer() from x = 1 nonlocal 'x' found. Nonlocal
the body of innermost() requires bindings can only be used
def inner(): inside nested functions.
two nonlocal bindings to reach x. nonlocal x
• In fact, a nonlocal statement x = 2
y = 3.14
allows multiple variables. In this
example, both x and y are nonlocal def innermost():
variables in innermost(). nonlocal x, y
x = 3; y *= 2
• Note that nonlocal bindings can print("innermost:", x)
only be used inside nested innermost()
functions. You can't use nonlocal print("inner:", x, y)
to refer to a global variable innermost: 3
defined outside the outer() inner() inner: 3 6.28
outer: 3
print("outer:", x)
function. global: 0
outer()
56
print("global:", x)
Scope and Lifetime of Variables
• The keyword global is used to x = 0
avoid variable shadowing and def outer():
assign to global variables. x = 1
def inner():
global x
x = 2
print("inner:", x) inner: 2
outer: 1
inner() global: 2
print("outer:", x)
outer()
print("global:", x)
57
Scope and Lifetime of Variables
• This code is a bit trickier. def outer():
x = "Old" inner is still the old
• The global statement inside the inner ❌ def inner():
function does not affect the variable x global x
x = "New"
defined in the outer function, which print("Before inner:", x)
keeps its value 'Old'. inner()
print("After inner: ", x)
• After calling inner(), a variable x exists
in the module namespace and has the outer()
print("In main:", x)
value 'New'. It can be printed at the
module level or main (i.e. outside the Before inner: Old
After inner: Old
functions). In main: New
58
Programming Advice
• Global variables are generally bad practice and should be avoided.
• It is okay to define some global “constants” (i.e., just for read-only purposes)
that may be used throughout the program.
• But in most cases, we should pass values as arguments to a function or return
values from it instead of reading or writing global variables.
59
Scope: Summary
• Python creates a new scope for each module, class, function,
generator expression, dict comprehension, set comprehension and in
Python 3.x also for each list comprehension.
• Apart from these, there are no nested scopes inside of functions.
60