Previous: 07-Containers.html
**If debugging is the process of removing software bugs, **
**then programming must be the process of putting them in.**
- https://en.wikipedia.org/wiki/Edsger_W._Dijkstra
It’s time to start creating a lot more bugs!
document.querySelector('video').playbackRate = 1.2
Quick overview:
* http://scipy-lectures.org/intro/language/functions.html
* https://automatetheboringstuff.com/2e/chapter3/
* https://books.trinket.io/pfe/04-functions.html
*
https://docs.python.org/3/tutorial/controlflow.html#defining-functions
* https://inventwithpython.com/invent4thed/chapter7.html
* https://inventwithpython.com/invent4thed/chapter8.html
* https://inventwithpython.com/invent4thed/chapter9.html
* https://inventwithpython.com/invent4thed/chapter10.html
* https://python.swaroopch.com/functions.html
* https://realpython.com/defining-your-own-python-function
* https://www.learnpython.org/en/Functions
* https://www.learnpython.org/en/Multiple_Function_Arguments
* https://www.python-course.eu/python3_functions.php
*
https://www.python-course.eu/python3_global_vs_local_variables.php
* https://www.python-course.eu/python3_namespaces.php
* https://www.python-course.eu/python3_passing_arguments.php
* https://www.tutorialspoint.com/python3/python_functions.htm
https://en.wikipedia.org/wiki/Subroutine
* A function can be considered a named, callable, list of
statements.
* A subroutine (a.k.a. function) is a sequence of program instructions
that should ideally performs a specific task, packaged as a named
unit.
* This unit can then be used (called) in programs wherever that
particular task should be performed.
https://en.wikipedia.org/wiki/Subroutine#Advantages
* Abstraction can improve readability. Does it
always?
* Enable modularity and re-use?
* Reduce redundancy?
* Decomposing a complex programming task into simpler steps: this is one
of the two main tools of structured programming, along with data
structures.
* Reduce duplicate code?
* Simplify testing. Does it always?
* Enable reuse of code across multiple programs?
* Divide a large programming task among various programmers, or various
stages of a project?
* Hide implementation details from users of the subroutine. Is this
always good?
* Improve readability of code by replacing a block of code with a
function call, where a descriptive function name serves to describe the
block of code?
* This makes the calling code concise and readable, even if the function
is not meant to be reused.
* Improve traceability (i.e. most languages offer ways to obtain the
call trace which includes the names of the involved subroutines and
perhaps even more information such as file names and line numbers); by
not decomposing the code into subroutines, debugging could be
impaired
https://en.wikipedia.org/wiki/Subroutine#Disadvantages
* Invoking a subroutine (versus using in-line code) imposes some
computational overhead in the call mechanism.
* A subroutine typically requires standard housekeeping code both at
entry to, and exit from, the function (function prologue and epilogue
usually saving general purpose registers and return address as a
minimum).
* Testing and debugging can also be more complicated.
A function definition states:
Functions are typically defined in the most global level scope in a
file, though can also be defined within functions.
Functions must be defined before they are called in the sequence of
statements in a program!
https://en.wikipedia.org/wiki/Scope_(computer_science)
https://en.wikipedia.org/wiki/Variable_(computer_science)#Scope_and_extent
* In many languages, variables in a function only exist within that
function, and within a function, the calling function’s variables are
not accessible to the called function, and vice-versa.
* In python, it’s a bit more nuanced that that (more below).
https://en.wikipedia.org/wiki/Parameter_(computer_programming)
Functions optionally take inputs:
* A parameter is a general specification for the unit
of potential input to a function (in the
declaration/definition).
* A parameter is a kind of variable, used in a subroutine to refer to
one of the pieces of data provided as input to the subroutine.
* An argument is an actual value passed into function’s
parameter, when the function is called (the actual
call).
* The actual pieces of data are the values of the arguments (often
called actual arguments or actual parameters) with which the subroutine
is going to be called/invoked.
* A function may have no parameters, a single parameter, or
multiple.
* Some languages allow default argument values to be specified for each
parameter (python does).
* In python, functions themselves are objects, can be passed as
arguments to other functions.
* Importantly, in python:
* All arguments are passed by assignment of the variable
alias/reference.
* Mutable arguments and immutable arguments apparently differ in their
behavior when being passed to a function (and yet do not actually
differ).
* Immutable arguments can not be modified, and thus are re-assigned upon
assignment to variables within functions, thus having no impact on the
original object passed into the function, from the scope above the
function call. This leads to the appearance of it having been passed by
value, rather than by reference (a non-python C/C++concept), when in
reality, it was just passed by assignment.
* Mutable arguments can be modified, and thus if they are modified
within the function, can have an impact on the original object passed
into the function, from the scope above the function call. This leads to
the appearance of it having been passed by reference, rather than by
value (a non-python C/C++ concept), when in reality, it was just passed
by assignment.
https://en.wikipedia.org/wiki/Return_statement
* In some languages, to return a value to main’s scope,
a function uses a special return statement.
* In some languages, the return type or variable is
part of the declaration/definition.
* This type-hint is optional but recommended in Python!
* There is often one return statement, but there can be multiple, as
there can be in Python.
* However, only one return statement is ever executed, because after it
executes, the function completes execution by returning execution to the
calling function.
Without type hints:
def func_name(arg1, arg2, defaulted_arg='somevalue'):
print('statements here')
some_local_var = 'someothervalue'
return some_local_var
With optional type hints (highly recommended):
def func_name(arg1: type, arg2: type, defaulted_arg: type = 'somevalue') -> return_type:
print('statements here')
some_local_var: type = 'someothervalue'
return some_local_var
Note: parameters and arguments in definitions and calls can be broken onto different lines (as above).
With parameters: parameters are specified by name,
in a comma-separated list.
08-Functions/functions_00_convert.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def ft_inch_to_cm(num_ft, num_inch):
num_cm = ((num_ft * 12) + num_inch) * 2.54
return num_cm
result_cm = ft_inch_to_cm(5, 6)
print(result_cm)
Type hints: technically optional, a form of comment, but required in
this class!
08-Functions/functions_01_convert_type.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def ft_inch_to_cm(num_ft: float, num_inch: float) -> float:
num_cm = ((num_ft * 12) + num_inch) * 2.54
return num_cm
result_cm: float = ft_inch_to_cm(5, 6)
print(result_cm)
Parameter and argument naming: arguments can be
named variables, and variable arguments don’t need to be named the same
as the parameter itself:
08-Functions/functions_02_convert_vars.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def ft_inch_to_cm(num_ft: float, num_inch: float) -> float:
num_cm = ((num_ft * 12) + num_inch) * 2.54
return num_cm
five: float = 5.0
# note argument name here vs. parameter name above
result_cm = ft_inch_to_cm(num_ft=five, num_inch=6.0)
print(result_cm)
NAME YOUR PARAMETERS UPON PASSING ARGUMENTS TO THEM in the actual
function call itself (a.k.a, keyword arguments)!!
Explicit is better than implicit.
Doing it this way makes your assignment unambiguous, even when order is
not as specified in the definition.
Named keyword arguments must come last, if you mix them with positional
arguments.
Without parameters: A function without parameters
has empty parentheses.
Note: this function also happens to return None, but that is not related
to its lack of parameters.
08-Functions/functions_03_noparam.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def print_hello() -> None:
print("Hello there.")
print_hello()
+++++++++++++++++++++++++
Cahoot-08.1
https://mst.instructure.com/courses/58101/quizzes/55735
What about order?
08-Functions/functions_04_print.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def print_hello() -> int:
four: int = 4
print("Hello there.")
return four
print(print_hello())
With return value: A function may print AND return
something
08-Functions/functions_05_return.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def print_hello() -> int:
four: int = 4
print("Hello there.")
return four
my_int = print_hello() # prints and returns value
print(my_int)
print and return are different!
Get the vocabulary straight!
print() prints to the screen!
return just returns a value from a function!
Without return value: A function that doesn’t return
a value uses “-> None”.
08-Functions/functions_06_noreturn.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def print_four() -> None:
four: int = 4
print(four)
print_four() # prints alone, but return value of None
var = print_four() # prints, and saves return value of None
print(print_four()) # prints and then prints returned value (None)
With array parameters:
08-Functions/functions_07_inarrays.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List, Any
def print_array(my_array: List[Any]) -> None:
for item in my_array:
print(item)
some_array: List[int] = [4, 3, 1]
print_array(some_array)
another_array: List[str] = ["hey", "you"]
print_array(another_array)
The easy promiscuity of functions like this, operating on any iterable container, in python is one of python’s more pleasant benefits!
+++++++++++++++++++++++++
Cahoot-08.2
https://mst.instructure.com/courses/58101/quizzes/55736
Returning arrays: What about returning an array from
a function??
08-Functions/functions_08_outarrays.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
def mod_array(some_array: List[int]) -> None:
some_array[1] = 1
some_array: List[int] = [0, 0, 0]
mod_array(some_array)
print(some_array)
Does this help?
08-Functions/functions_09_outarrays.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
def mod_array(my_array: List[int]) -> List[int]:
my_return_array = my_array
my_return_array[1] = 1
return my_return_array
some_array: List[int] = [0, 0, 0]
another_array = mod_array(some_array)
print(some_array)
print(another_array)
This actually prevents modification of the global list object.
08-Functions/functions_10_outarrays.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
def mod_array(my_array: List[int]) -> List[int]:
my_return_array = my_array.copy()
# This would also work:
# my_return_array = my_array[:]
my_return_array[1] = 1
return my_return_array
some_array: List[int] = [0, 0, 0]
another_array = mod_array(some_array)
print(some_array)
print(another_array)
+++++++++++++++++++++++++
Cahoot-08.3
https://mst.instructure.com/courses/58101/quizzes/55737
Scoping
08-Functions/functions_11_scope.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def print_what() -> None:
g: int = 3
i: int = 5
print(g, i)
g: int = 4
print_what()
print(g)
print(i)
Uses: functions can be used as expressions
* A function call’s arguments can be expressions. Ex:
z = ft_inch_to_cm(x, y + 1)
* A function call may appear in an expression. Ex:
z = 1.0 + ft_inch_to_cm(5, 6)
* A print() statement’s item may be an expression, so a call may appear
there, as below.
08-Functions/functions_12_directprint.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
def ft_inch_to_cm(num_ft: float, num_inch: float) -> float:
num_cm = ((num_ft * 12) + num_inch) * 2.54
return num_cm
# Function returns walue as the result of evaluating
# the expression containing a function
print(ft_inch_to_cm(5, 6))
Built-in math and functions
08-Functions/functions_13_math.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import math
x: float = float(input())
y: float = float(input())
# python's math import:
print(math.sqrt(x))
print(math.pow(x, y))
# built-in:
print(abs(x))
Built-in random functions
08-Functions/functions_14_random.py
08-Functions/functions_15_sqrt.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import math
import numpy
def sqare_rt(my_val: float) -> float:
diff: float = 1.0
root: float = 1.0
while (diff > 0.0001) or (diff < -0.0001):
root = (root + (my_val / root)) / 2.0
diff = (root * root) - my_val
return root
twenty: float = 20.0
# Our version:
print(sqare_rt(20.0))
# Versus python's:
print(math.sqrt(20.0))
# Versus numpy's
print(numpy.sqrt(20.0))
http://inventwithpython.com/invent4thed/chapter5.html
08-Functions/functions_16_dragon.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import random
import time
def displayIntro() -> None:
print(
"""You are in a land full of dragons. In front of you,
you see two caves. In one cave, the dragon is friendly
and will share his treasure with you. The other dragon
is greedy and hungry, and will eat you on sight.\n"""
)
def chooseCave() -> str:
cave = ""
while cave != "1" and cave != "2":
cave = input("Which cave will you go into? (1 or 2)")
return cave
def checkCave(chosenCave: str) -> None:
print("You approach the cave...")
time.sleep(2)
print("It is dark and spooky...")
time.sleep(2)
print("A large dragon jumps out in front of you! He opens his jaws and...")
print()
time.sleep(2)
friendlyCave = random.randint(1, 2)
if chosenCave == str(friendlyCave):
print("Gives you his treasure!")
else:
print("Gobbles you down in one bite!")
def main() -> None:
playAgain = "yes"
while playAgain == "yes" or playAgain == "y":
displayIntro()
caveNumber = chooseCave()
checkCave(caveNumber)
print("Do you want to play again? (yes or no)")
playAgain = input()
if _name_ == "_main_":
main()
http://inventwithpython.com/invent4thed/chapter14.html
* We have covered variables, branching, loops, and arrays.
* What else could be left?
* The code we had was one big mass, left for you to decipher, with no
neat labels, or convenient abstractions.
* Parts of it could be made re-usable in other programs!
08-Functions/functions_17_caesar.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from typing import List
import random
caesar_encoding: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ "
def key_gen() -> int:
"""
Generates one Caesar key
"""
key = random.randint(1, 25)
return key
def str_to_num_arr(message: str) -> List[int]:
"""
Translates a string into a Caesar-encoded List
"""
arr: List[int] = []
for character in message:
arr.append(caesar_encoding.find(character.upper()))
return arr
def num_arr_to_str(encoded_arr: List[int]) -> str:
"""
Translates a Caesar encoded list back into a string
"""
plaintext: List[str] = []
for encoded_char in encoded_arr:
plaintext.append(caesar_encoding[encoded_char])
plaintext_string = "".join(plaintext)
return plaintext_string
def translate(encoded_arr: List[int], mode: int, key: int) -> List[int]:
"""
Encrypts or decryps a Caesar-encoded List of ints
"""
translated: List[int] = []
for encoded_char in encoded_arr:
if mode == 1:
# 27 is the symbol set size (# letters in alphabet)
# Note the space added above (bug from last time)!
translated.append((encoded_char + key) % 27)
else:
translated.append((encoded_char - key) % 27)
return translated
message: str = input("\nEnter your message, in English:\n")
gen_key: str = input("Want to generate a key? (y/n)")
if gen_key == "y":
ok: int = 0
while ok == 0:
key = key_gen()
print("Your key is: ", key)
print("Is the key it ok with you (1-yes, 0-no, make another): ")
ok = int(input())
else:
key = int(input("What is your key (0-25)?"))
print("\nYour Caesar key is: ")
print(key)
print("\n Share this with your partner. Don't tell anyone else\n")
print("\nEnter 1 for encryption, and 0 for decryption: ")
mode: int = int(input())
message_arr = str_to_num_arr(message)
message_arr = translate(message_arr, mode, key)
message = num_arr_to_str(message_arr)
print(message)
To be stepped through in the python3-spyder IDE and/or python3-pudb
debugger:
08-Functions/functions_18_overview.py
+++++++++++++++++++++++++
Cahoot-08.4
+++++++++++++++++++++++++
Cahoot-08.5
+++++++++++++++++++++++++
Cahoot-08.6
+++++++++++++++++++++++++
Cahoot-08.7
+++++++++++++++++++++++++
Cahoot-08.8
When you nest function calls, you can check and set the limit of the
number of possible function calls on the stack in python:
* https://docs.python.org/3/library/sys.html#sys.getrecursionlimit
* https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
*
https://docs.python.org/3/library/inspect.html#the-interpreter-stack
08-Functions/functions_19_depth.py (While tracing, not the stack window in pudb3!)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import inspect
# defaults to 1000 in python3, and 3000 in ipython3?
print(sys.getrecursionlimit())
sys.setrecursionlimit(1500)
print(sys.getrecursionlimit())
# 1 in python3
# 12 in ipython3
# Overhead of extras in ipython3
print("\nIn global/main")
print(len(inspect.stack()))
def func1() -> None:
print("\nIn func1")
print(len(inspect.stack()))
func2()
def func2() -> None:
print("\nIn func2")
print(len(inspect.stack()))
func3()
def func3() -> None:
print("\nIn func3")
print(len(inspect.stack()))
func1()
The sys.
functions are not aptly named, since it applies
generally to function call depth.
It is however typically only relevant when doing recursion, because you
might actually reach that depth.
Namespaces and scopes are related constructs:
https://en.wikipedia.org/wiki/Namespace
* In computing, a namespace is a set of signs or
symbols (names) that are used to identify and refer to objects of
various kinds.
* A namespace ensures that all of a given set of objects have unique
names, so that they can be easily identified, and there are often
multiple namespaces within a program.
* Namespaces are often structured as hierarchies to allow reuse of names
in different contexts.
* As a rule, names in a namespace do not generally have more than one
meaning; that is, different meanings cannot share the same name in the
same namespace (unless one masks the other in some cases).
* A namespace is also called a context, because the same name in
different namespaces can have different meanings, each one appropriate
for its namespace.
* A namespace (sometimes also called a name scope), is an abstract
container or environment created to hold a logical grouping of unique
identifiers or symbols (i.e. names).
* An identifier defined in a namespace is associated only with that
namespace.
* An equally named identifier can be independently defined in multiple
namespaces.
* That is, an identifier defined in one namespace may or may not have
the same meaning as the same identifier defined in another
namespace.
* Languages that support namespaces specify the rules that determine to
which namespace an identifier (not its definition) belongs.
https://en.wikipedia.org/wiki/Scope_(computer_science)
https://en.wikipedia.org/wiki/Variable_(computer_science)#Scope_and_extent
* The scope of a name binding, an association of a name
to an entity, such as a variable, is the part of a program where the
name binding is valid, that is, where the name can be used to refer to
the entity.
* In other parts of the program the name may refer to a different entity
(it may have a different binding), or to nothing at all (it may be
unbound).
* The scope of a name binding is also known as the visibility of an
entity, particularly in older or more technical literature; this is from
the perspective of the referenced entity, not the referencing
name.
* The term “scope” is also used to refer to the set of all name bindings
that are valid within a part of a program or at a given point in a
program, which is more correctly referred to as context or
environment
+++++++++++++++++++++++++
Cahoot-08.9
https://mst.instructure.com/courses/58101/quizzes/55858
+++++++++++++++++++++++++
Cahoot-08.10
https://mst.instructure.com/courses/58101/quizzes/55859
https://realpython.com/python-main-function/
More to come on main in 09-ModulesPackages.html
Next: 09-ModulesPackages.html