################# Notes on Functions in Python ##################### ######################################################################## ## Functions (also called procedures or "methods") form the most important # component in a well structured program. When we design and implement an # algorithm we want to be able to use it multiple times, on different # input values. Furthermore, we want our implementation code to be # self-contained so that we can easily integrate it into larger programs. ## Let's start with a relatively simple example. def firstfun(): i = 0 while i<5: print("hello") i += 1 # while # end of firstfun # As with if statemens and while loops, the body of the definition is # indicated by indentation and alignment. If you typed the above into # the python interpreter, NOTHING HAPPENS. Because you only DEFINED a # function called 'firstfun', but you haven't CALLED the function firstfun() # this calls the function and prints hello 5 times. # The idea of a function is that you can call it multiple times: firstfun() # prints another 5 hellos # However, since this function does exactly the same thing each time it's # called, it's probably pointless to call it more than once. To make # functions more interesting, we *parameterize* the function with variables: def morefun(n): # function to print hello n times i = 0 while ib: a = a%b else: b=b%a # while return a+b ## This is the value returned by the function. #gcd print(gcd(8,12)) # prints 4 ## function to return the nth Fibonacci number: def fib(n): a,b = 1,1 # initialize the first two fib numbers while n>1: a,b = b,a+b # compute the next two fib numbers n -= 1 # n= n-1 # while return b #fib ### A function can also take no parameters: def f(): print("Didn't get any arguments") #Such functions may still be useful if they have access to variables, #other functions defined externally. ############## IMPORTANT THINGS TO WATCH OUT FOR ############### # Here are many common mistakes made by beginners when writing functions. ## 1. Confuse defining a function with calling a function. # When you define a function using *def*, absolutely nothing happens. # You need to CALL it in order for the code of the function to be executed. ## 2. Confuse where to put I/O (input/output). # Sometimes I see beginners write code like this: def circumference(r): r = float(input("enter radius")) return 3.1415927 * r *2 # or def circumference(r): print(3.1415927 * r * 2) ## The problem with these functions is that they do not SEPARATE IO FROM # COMPUTATION. In general, you should not put input and print statements # inside a function. A function can be used in a variety of I/O settings, # for example, using a graphical user interface, or taking input from a # network connection. The function should focus on the ALGORITHM that # it's supposed to implement, and leave IO to other parts of the program. # Thus we should arrange IO to be outside the function definition: n = int(input("enter a number:")) # input answer = fib(n) # computation print("the", n, "th Fibonacci number is", answer) # output ## There are two exceptions to not putting print statements inside functions: # 1. when the only purpose of the function is to print something. For # example, when you want to print a large set of data in a certain format, # and you want to encapsulate the procedure for printing it in the right form. # 2. when you want to TRACE your program for debugging purposes, you can # temporarily insert print statements into your program to see what your # code is doing or not doing at certain points. These print statements # should be commented out or removed after debugging. ######## Other BAD examples of functions: # function that doesn't ALWAYS return a value: def f(n): if (n%2==0): return 0 # ### This function cannot be used as an expression because it doesn't always # return a value. If you call print(f(1)), it will result in an error. ### But a function may not always be able to compute a value: for example def squareroot(n): if n>=0: return n**0.5 else: print("negative numbers do not have real square roots") ### # This is another function that does not always return a value, but # one might argue that it shouldn't (although you could return a complex # number, like 1+2j, which python recognizes). In such situations, instead of # printing inside the function, you should **raise an exception**: def squareroot(n): if n>=0: return n**0.5 else: raise Exception("invalid argument "+str(n)+" to squareroot") # ## This is better than just printing the error message: the program # will terminate with an error message. And as we will learn later, # exceptions can be "caught" and handled gracefully. ####### How to comment a function. Large software systems are written by # multiple programs. When you write a function, you need to provide # enough information to a programmer so that he or she knows how to call # the function without having to look at how the function is written. # Specify what each input parameter should represent and what return value # (if any) should be expected. Be especially clear as to the *type* of # the parameters and return value. # Novice programmer: How much commenting is enough? # Master programmer: It's never enough! #### Exercise: write a function to compute n! (n-factorial). For example, # 5! is 5 * 4 *3 * 2 * 1, 3! is 3* 2 * 1. 0! is 1. The function should take # n as an argument and return n! as a value. If n is negative, your # function should raise an exception. ######### Using functions with arrays: # For more interesting functions, we can can use loops with arrays. # arrays can be passed as arguments to a function, as well as be # returned by a function as its value. ### Function to return the sum of all numbers in an array. def sum(A): # return sum of all numbers inside array A ax = 0 # accumulator i = 0 # loop counter/array index while ianswer: answer = b if c>answer: answer = c return answer ## Can you generalize this algorithm into a loop on an array of numbers? ## answer to exercise: def largest(A): answer = A[0] # accumulator holds the value computed so far i = 1 # array index starts at 1 since 0 already accounted for while i answer: answer = A[i] i += 1 # while return answer ##largest ############ #### When writing a function, you need to first be clear as to the following: # 1. What precisely is the function supposed to compute # 2. How many parameters does the function take # 3. What does each parameter represent # 4. What is the *type* of each parameter (int, string, array, etc...) # 5. Whether the function should return a value # 6. The type and purpose of the return value, if there is one. ############ ################ MORE ON LOCAL VARIABLES: #################### # Does "x" always refer to the same value? x = 1 def inc(x): # function that changes value of x!! x = x+ 1 print(x) # this will print 2 #inc inc(x) # call inc function on x print("x is now",x) # This WILL PRINT 1, NOT 2! # Why? Because there are two different variables named x. The x # inside the function definition is isolated from other variables that # might be called x in the rest of your program. It's similar to words like # "it." "It" means different things in different contexts. Inside # a function, all parameter (argument) variables, and all variables THAT ARE # ASSIGNED TO in the body of the function, are local to the function. They # cannot be referred to outside of the function. #Read and consider this more complicated example carefully: a = 1 b = 2 c = 3 def f(a): d = a + b c = d + 1 return c #f print(f(5)) # prints 8 print(a) # prints 1 print(b) # prints 2 print(c) # prints 3 #print(d) # ERROR. d is not defined here ## In a modern programming language, VARIABLES have scope. You probably # already sensed this: the argument variable A in the sum and indexof functions # above are not the same: their meaning and usage is local to a function. # But the story is a bit more complicated, and Python has a very specific # set of rules governing the scope of visibility of variables. ## The "shocker" is that there are 2 variables named *a* in the program: # one has "global" scope (a = 1), and one exists only inside the function. # the a inside the function is created when the function is called, and # initially assigned the value of the parameter passed in. However, # when the function exits (after it returns), the variable *a* still refers # to the a=1 defined outside the function. ## There are also 2 variables named *c* in the program: one is declared by # c=3, and exists globally, and the other is declared by c = d + 1 # inside the function, and is only used inside the function. When the # function call ends, *c* will again refer to the global c. ## There is a variable named *d*, but it exists only inside function calls # to f. d was never assigned a value outside the function, so it is only # meaningful inside the function call. ## However, there is only one *b* in the program, which refers to b=2. # The *b* outside the function and the *b* inside the function are the same. # This is because the code inside f only READ the value of *b*, it did not # attempt to WRITE (assign) to it. In contrast, it did assign a value # to *c*, and that's why *c* became a local variable but *b* did not. ## SO REMEMBER: ANY VARIABLE YOU ASSIGN TO INSIDE A FUNCTION BECOMES LOCAL ## TO THAT FUNCTION. ALL PARAMETER VARIABLES ARE ALSO LOCAL TO THE FUNCTION. # Parameter variables (formal arguments) are local because they are ASSIGNED # to actual arguments when the function is called. ####### What does the following code do? (study carefully): def swap(x,y): x,y = y,x # tries to swap the two values. # swap x = 1 y = 2 swap(x,y) print(x,y) # what will this print? # ANSWER: it still prints 1,2. WHY????? #There is a way for a function to change the value of an external variable #by declaring it "global": c = 1 def f(n): global c c = c + n # f(2) print(c) # c is now 3. ## HOWEVER, the global definition is to be used sparingly. There is a reason # why we want to limit the scope of variables. A function should be a # "plug and play" piece of code: it should be usable as a part of many # different programs. In general, we do NOT want assignments to # variables inside the function to interfere with other values outside # of the function. We want to function to be self-contained. This # also makes changing or debugging the function much easier. The # highest cost of software development is usually not in getting it to # work initially, but in being able to maintain and upgrade it over time. # Most of the variables that we use will be local inside functions (or # inside classes, as we'll learn later). Global variables should be used # sparingly. Some programming languages such as Java do not even allow # any variables to be global. ## Finally, just as we like to use x, y, z, etc. to name our variables, we # also sometimes use f, g, h to name functions. Function names can also # clash. Python also allows a function to be defined locally inside another # function. def f(n): def g(x): return x*x # end of inner function g return g(n-1) # body of outer function f # end of function f # print(g(2)) # error: the function g is only visible inside the body of f.