Recall: We have already seen that a variable is a named memory location bound to some value.
The fact we have a name allows us to access such value later on, and having a name makes it easier to remember, as opposed to the physical address in memory.
The idea of binding a name to a memory location can be extended to functions as well. We can look at a function then as a name bound to some code in memory.
In other words, the value in this case is simply a block of code to be executed later, when we actually call or invoke the function.
We will start with calling some built-in functions. Calling a function invokes the code the function is bound to.
Arguments may be given to the function to alter its behavior.
All functions return a value. If not explicitly specified the return value is None
.
ret = type(42)
print(ret)
# What does the print() function return?
print( print() )
<class 'int'> None
Python offers some useful functions for converting one value to another. You may know this as typecasting.
int(str)
: string to intint(float)
: float to intfloat(str)
: string to floatfloat(int)
: int to floatstr(int)
: int to stringstr(float)
: float to stringprint( int('32') )
print( int(3.4) )
print( int(-2.3) ) # truncates
print()
print( float('3.14') )
print( float(10) )
print()
print( str(32) )
print( str(3.14) )
32 3 -2 3.14 10.0 32 3.14
Python provides the math
module with all the expected mathematical functions you may have used or may need to use. To use any module you must first import it.
import math
The import creates a module object
which allows you to access all the defined module properties (variables, functions, etc)
print( type(math) )
print(math.pi)
<class 'module'> 3.141592653589793
You define a function with def
, followed by the function name
, a set of parentheses ()
and a colon :
. If there are no parameters to the function, the parentheses are empty.
That makes up the function header. The body
of the function is next and each statement must be indented (conventionally 4 spaces). Indentation is how Python determines the body.
# A function definition
def functionName(optional parameters): # The function header
functionBody # The body
A function definition creates yet another object; a function object of type function
.
def hello():
print("Hello")
print(hello)
print( type(hello) )
<function hello at 0x106d8f820> <class 'function'>
Since the program is parsed starting at the top moving down, a function defintion has to be read first (function object created) before you can call it. Same rule as variables; must define first before you use them.
ohNo()
def ohNo():
print('Sorry')
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /var/folders/_5/pbybv9c90j77vqh9wby2v8140000gq/T/ipykernel_93924/2890711889.py in <module> ----> 1 ohNo() 2 3 def ohNo(): 4 print('Sorry') NameError: name 'ohNo' is not defined
Arguments are either positional or keyword.
Positional arguments depend on the argument's position in the argument list and are always required.
Basically each argument is copied to its respective parameter, i.e. first arg to first param, and so on.
Take a look at the display()
function. It takes three parameters: a
, b
, and c
.
When calling the function it is given the three arguments: 1
, 2
, and 3
, all integer literal values. Integer variables could also have been used.
def display(a, b, c):
print(a, b, c)
display(1, 2, 3)
1 2 3
Arguments are passed by name, not position. Each argument is preceded by a keyword that helps in readability.
def display(a, b, c):
print(a, b, c)
# call using keywords
display(a = 1, b = 2, c = 3)
# keyword arguments don't have to match parameter order
display(b = 2, a = 1, c = 3)
1 2 3 1 2 3
You may combine positional with keyword arguments, but care must be taken.
Rule is: keyword arguments must follow positional ones.
def display(a, b, c):
print(a, b, c)
# a by position, b and c by keyword
display(1, c = 3, b = 2)
1 2 3
# error if rule not followed
display(b = 2, c = 3, a)
File "/var/folders/_5/pbybv9c90j77vqh9wby2v8140000gq/T/ipykernel_93924/875835493.py", line 2 display(b = 2, c = 3, a) ^ SyntaxError: positional argument follows keyword argument
A parameter may have a default value. If it does, then you can omit its argument when calling the function, thus making the argument optional.
def fullName(first, last = 'Smith'):
print(first, last)
# call with both arguments given
fullName('John', 'Doe')
# call with first name only given
fullName('John')
John Doe John Smith
Scope determines the visibility of a name, i.e. what code can access the name directly.
The scope of a function's parameters and local variables is the function itself.
def print_var():
local_var = 1
print(local_var)
print_var()
print(local_var)
1
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /var/folders/_5/pbybv9c90j77vqh9wby2v8140000gq/T/ipykernel_93924/1341840104.py in <module> 4 5 print_var() ----> 6 print(local_var) NameError: name 'local_var' is not defined
A variable defined outside a function is accessible within the function, but for reading only.
def print_var():
# can access global_var for reading
print('In function:', global_var)
global_var = 1
print_var()
print('In module:', global_var)
In function: 1 In module: 1
A variable assigned to inside the function shadows a variable defined outside the function.
def print_var():
# This in now a local variable.
global_var = 2
print('In function:', global_var)
global_var = 1
print_var()
print('In module:', global_var)
In function: 2 In module: 1
The global
keyword can be used to allow a function to update a variable defined outside the function.
def change_var():
# This refers to the global variable.
global global_var
global_var = 2
print('In function:', global_var)
global_var = 1
change_var()
print('In module:', global_var)
In function: 2 In module: 2
Arguments are always passed by value to a function.
In Python everything is an object which means variables hold references to such objects in memory.
Note however, some types are immutable (can't be changed) and some mutable (can be changed).
This distinction means that despite the fact that the parameter holds the same reference as the argument, if the type is immutable, a copy of the value is made instead.
def f(p):
# p now refers to a new memory location with value 2
p += 1
print('In function:', p)
a = 1
print('Before call:', a)
# a and p refer to same value 1.
f(a)
# a still refers to its orginal value 1
print('After call:', a)
Before call: 1 In function: 2 After call: 1
So, above a
and p
are of type int
, a scalar type, and thus immutable. When the value of p
is changed, a new memory location is created that holds the new value, leaving the argument a
unaffected.
Note: A Python source file (.py
) is referred to as a module
. When you run the script, the name of the module is identified as __main__
.
Mutable object types will be affected by functions as we will see below with a list (cover them later).
Keep in mind that what happens with such types is the reference itself cannot change (pass by value remember), but the actual instance or value being referenced can be changed.
def change(p):
print('In function:', p)
p[0] = 10
a = [1, 2]
print('Before call:', a)
change(a)
print('After call:', a)
Before call: [1, 2] In function: [1, 2] After call: [10, 2]
Create a separate Python source file (.py) in VSC to complete each exercise.
A function can call another function as you well know or guessed. So, for this exercise define two functions:
displayMessage()
- takes a string message to print out
repeatMessage()
- takes the message to display calls the displayMessage()
twice, passing it the message.
Sample output:
Pizza!
Pizza!
Define a function that displays the following grid:
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
Hint: to print more that one value on a line, you can print a comma-separated sequence of values:
print('+', '-')
By default, print()
advances to the next line, but you can override that behavior and put a space at the end, like this:
>
print('+', end=' ')
print('-')
The output of these statements is: '+ -'
.
A print statement with no argument ends the current line and goes to the next line.
Expand the grid2.py
program to draw a 4 x 4 grid.
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +