0% found this document useful (0 votes)
5 views61 pages

Lec04-Functions (New)

The document is a lecture outline on functions in Python programming, covering topics such as defining functions, parameter passing, return statements, and built-in functions. It emphasizes the importance of modular programming for code reusability and maintainability. Additionally, it provides examples and explanations of various function types, including user-defined functions and their characteristics.

Uploaded by

jialinli865
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)
5 views61 pages

Lec04-Functions (New)

The document is a lecture outline on functions in Python programming, covering topics such as defining functions, parameter passing, return statements, and built-in functions. It emphasizes the importance of modular programming for code reusability and maintainability. Additionally, it provides examples and explanations of various function types, including user-defined functions and their characteristics.

Uploaded by

jialinli865
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/ 61

CSCI1550

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

• Reuse many times for different cases or input data (arguments).


fib(100) 0 1 1 2 3 5 8 13 21 34 55 89

fib(500) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

fib(2000) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597



5
Types of Functions
• Built-in functions - Functions that are built into Python.
• User-defined functions - Functions defined by the programmer.

• And functions defined in a class are usually called methods.

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

Example: The round() Function


• Some more subtleties: >>> round(2.85, 1) Why not 2.8 if rounding
2.9
half to even!?
>>> round(2.75, 1)
2.8
>>> round(2.65, 1)
2.6
>>> round(2.55, 1)
2.5
Why not 2.6 if rounding
half to even!?

• 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

Example: The round() Function


• If we print the floating-point representations with higher precision,
we will see the trick.
>>> round(2.85, 1) >>> f"{2.85:.20f}"
2.9 '2.85000000000000008882' 2.85 rounds up
>>> round(2.75, 1) >>> f"{2.75:.20f}"
2.8 '2.75000000000000000000' 2.75 rounds up
>>> round(2.65, 1) >>> f"{2.65:.20f}"
2.6 '2.64999999999999991118' 2.65 rounds down
>>> round(2.55, 1) >>> f"{2.55:.20f}"
2.5 '2.54999999999999982236' 2.55 rounds down

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

• The keyword def introduces a function definition. It must be followed


by the function name and a parenthesized list of formal parameters.

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

• To call the function:


fib(2000)

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

12
Control Flow Across Function Calls
print("At the beginning ...")

• When you call a function, program def main():


control is transferred to the print("Now, inside the main function.")
function. The function body gets fib(100)
print("Came back to the main function.")
executed, then the control don't excecute now
returns to the caller. def fib(n):
At the beginning ... """Print a Fibonacci series up to n."""
Program entry point ... print("Now, inside the fib function.")
Now, inside the main function. a, b = 0, 1
Now, inside the fib function. while a < n:
0 1 1 2 3 5 8 13 21 34 55 89 print(a, end=' ')
Came back to the main function. a, b = b, a+b
That's the end! print()
Control transfers if __name__ == "__main__":
Control returns print("Program entry point ...")
Body execution main()
A defined function won't print("That's the end!")
No execution execute until you call it. 13
Defining Functions
• Defining a function creates a function object, which has type
function. Memory address of the function object
>>> print(fib) 12 hex digits * 4 = 48-bits
<function fib at 0x7f91de268e50> (used for 64-bit system)
>>> type(fib)
<class 'function'>

• 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

>>> f100 = fib2(100) # call it


>>> f100
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
15
Detour

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]

• Writing return without an expression argument returns None.


• Falling off the end of a function also returns None.
def f1(): def f2(): def f3():
print("Hello World") print("Hello World") print("Hello World")
return return None

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"

insects = ["bee", "butterfly", "caterpillar", "dragonfly", "ladybug"]


pos, bug = search(insects, "ladybug")
print(pos, bug) Output:
pos, bug = search(insects, ”mantis") 5 ladybug
print(pos, bug) None Not found
19
Multiple Return Statements
• A function can have multiple return statements. If reaching the end
of a function without meeting a return statement, None is returned
automatically.
• Example: def is_divisible(a, b):
if not a % b:
return True
else:
return False

• However, having too many return statements in a function may make


it hard to understand the function's logic. Having more than 3 return
statements in a function is usually a bad idea.

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?')

2. Giving one of the optional arguments:


ans = ask_ok('Are you sure to quit?', 2)

3. Giving all arguments:


ans = ask_ok('Are you sure to quit?', 2, 'Yes or no please!')

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

>>> ans = ask_ok('Are you sure to quit?', 2, 'Yes or no please!')


Are you sure to quit? hi
Yes or no please!
Are you sure to quit? bye
Yes or no please!
Are you sure to quit? oops
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in ask_ok
ValueError: invalid user response
24
Default Argument Values
• What is the output of this code? Although change the value of
default, the arg has confirmed, so
default = 2 no change.

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

File "<stdin>", line 1


SyntaxError: non-default argument follows default argument

• Remember that if you specify arguments with default values, they


must come after arguments without default values.
• So, the above should be fixed as:
def display(mesg, code=123):
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

>>> sample = open('sample.txt', 'w')


>>> print('Victoria', file=sample)
>>> sample.close()

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)

• All the following calls are valid:


display("Peter") # 1 positional argument Hello, Peter
display("Peter", "punch") # 2 positional arguments Take this! Peter
display("Peter", "greet", "Thank you") # 3 positional arguments Thank you Peter
display(name="Peter") # 1 keyword argument Hello, Peter
display(name="Peter", action="punch") # 2 keyword arguments Take this! Peter
display(name="Peter", action="greet", mesg="Thank you") # 3 kw args Thank you Peter
display("Peter", mesg="Thank you", action="greet") # 1 pos arg, 2 kw args Thank you Peter
display("Peter", "punch", mesg="Thank you") # 2 pos args, 1 kw arg Take this! Peter
display("Peter", mesg="Thank you") # 1 pos arg, 1 kw arg Thank you Peter

30
Precautions of Using Keyword Arguments
• For this function: def display(name, action="greet", mesg="Hello,"):
...

• But all the following calls would be invalid:


>>> display()
TypeError: display() missing 1 required positional argument: 'name'

>>> display(action="punch", "Peter")


SyntaxError: positional argument follows keyword argument

>>> display("Peter", name="Peter")


TypeError: display() got multiple values for argument 'name'

>>> display("Peter", actor="John")


TypeError: display() got an unexpected keyword argument 'actor'
31
Precautions of Using Keyword Arguments
• In a function call, keyword arguments must follow positional
arguments.
• A programmer cannot duplicate an argument by specifying it as both,
a positional argument and a keyword argument.
• All the keyword arguments passed must match one of the arguments
accepted by the function.

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

def func(arg, *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.

>>> def concat(sep, *args):


... return sep.join(args)
...

>>> concat('/', "earth", "mars", "venus")


'earth/mars/venus'

>>> concat(sep='/', "earth", "mars", "venus")


...
SyntaxError: positional argument follows keyword argument

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]

>>> sum_of_4(x) # doesn't work! or sum_of_4(x[0], x[1], x[2], x[3])


...
TypeError: sum_of_4() missing 3 required positional arguments: 'b', 'c', and 'd'

>>> sum_of_4(*x) # works now


10

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

parameter annotations return annotation


• Example: string
def movie_info(title: str, year: int, rating: float = 3.5) -> str:
return f"'{title}' released in {year} is rated {rating:.1f} stars."

print(f"{movie_info.__name__}'s signature: {movie_info.__annotations__}")


print(movie_info("The Green Mile", 1999, 5))
print(movie_info("Poor Things", 2023))

movie_info's signature: {'title': <class 'str'>, 'year': <class 'int'>, dictionary


'rating': <class 'float'>, 'return': <class 'str'>}
'The Green Mile' released in 1999 is rated 5.0 stars.
'Poor Things' released in 2023 is rated 3.5 stars.

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

print(movie_info("Poor Things", 2023))


Annotations: {'t': 'movie title (str)', 'y': 'release year (int)',
'rating': 'between 0 and 5 (float)', 'return': 'formatted string’}
'Poor Things' released in 2023 is rated 3.5 stars.
38
(Optional)

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

def f(n: int) -> Union[list[int], str]:

• Annotating a void function:


def foo(bar: int) -> None:
# ...
return None This statement is generally unnecessary.

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

def print_twins(start, end):


assert start > 1
for i in range(start, end-1):
if is_prime(i) and is_prime(i+2):
print(f"({i}, {i+2})")
42
Inner Functions
• What to do if you don't want to let the outside world call the
is_prime() function which is now treated as a "private" tool for the
print_twins() function?
def print_twins(start, end):
• You can define it as an assert start > 1
inner function in the
body of the outside def is_prime(n):
for i in range(2, n):
function! if n % i == 0:
• Not possible in C/C++. return False
return True
• Some people call this
encapsulation, which has for i in range(start, end-1):
a broader meaning in OOP. if is_prime(i) and is_prime(i+2):
print(f"({i}, {i+2})")

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

# define a lambda function


lambda_cube = lambda y: y*y*y

• All the following calls give the same result.


print(cube(5)) # prints 125

print(lambda_cube(5)) # prints 125

print((lambda y: y*y*y)(5)) # prints 125

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.

• Local Scope - a variable created inside a function belongs to the local


scope of that function, and can only be used inside that function.

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

• What is the output? def my_func():


x = 3
print(x)

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

You might also like