For those new to programming, here’s a short digression, adapted from chapter 8 of Edsger Dijkstra’s book, A Discipline of Programming[Dijkstra76].
Let’s say we need to set a variable, m, to the larger of two input values, a and b. We start with a state we could call “mundefined”. Then we want to execute a statement after which we are in a state of (m=
aorm=b)andm≤aandm≤b.
Clearly, we need to choose correctly between two different assignmentstatements. We need to do either ‘m=a’ or ‘m=b’. How do we make this choice? With a little logic, we can derive the condition by taking each of these statement’s effects out of the desired end-state.
For the statement ‘m=a’ to be the right statement to use, we show the effect of the statement by replacingm with the valuea, and examining the end state: (a=aor a=b)anda≤aanda≤b. Removing the parts that are obviously true, we’re left witha≤b. Therefore, the assignment ‘m=a’ is only useful when ‘a <= b’. For the statement m=b to be the right statement to establish the necessary condition, we do a similar replacement of bformand examine the end state: (b=aorb=b)andb≤aandb≤b. Again, we remove the parts that are obviously true and we’re left with b≤a. Therefore, the assignment ‘m=b’ is only useful when ‘b <= a’.
Each assignment statement can be “guarded” by an appropriate condition. if a>=b: m=a
elif b>=a: m=b
Is the correct statement to setmto the larger of aorb.
Note that the hard part is establishing the post condition. Once we have that stated correctly, it’s relatively easy to figure the basic kind of statement that might make some or all of the post condition true. Then we do a little algebra to fill in any guards or loop conditions to make sure that only the correct statement is executed.
1. The body condition must be initialized properly.
2. At the end of the suite, the body condition is just as true as it was after initialization. This is called theinvariant , because it is always true during the loop.
3. When this body condition is true and the while condition is false, the loop will have completed properly. 4. When the while condition is true, there are more iterations left to do. If we wanted to, we could define a mathematical function based on the current state that computes how many iterations are left to do; this function must have a value greater than zero when the while condition is true.
5. Each time through the loop we change the state of our variables so that we are getting closer to making the while condition false; we reduce the number of iterations left to do.
While these conditions seem overly complex for something so simple as a loop, many programming problems arise from missing one of them.
Gries recommends putting comments around a loop showing the conditions before and after the loop. Since Python provides the assert statement; this formalizes these comments into actual tests to be sure the program is correct.
Designing a Loop. Let’s put a particular loop under the microscope. This is a small example, but shows all of the steps to loop construction. We want to find the least power of 2 greater than or equal to some number greater than 1, call itx. This power of 2 will tell us how many bits are required to representx, for example.
We can state this mathematically as looking for some number,n, such that2n−1< x≤2n. If xis a power of 2, for example 64, we’d find26. If xis another number, for example 66, we’d find26<66≤27, which is
64<66≤128.
We can start to sketch our loop already. assert x > 1
... initialize ... ... some loop ...
assert 2**(n-1) < x <= 2**n
We work out the initialization to make sure that the invariant condition of the loop is initially true. Sincex must be greater than or equal to 1, we can setnto 1. 21−1= 20= 1< x. This will set things up to satisfy
rule 1 and 2. assert x > 1
n= 1
... some loop ...
assert 2**(n-1) < x <= 2**n
In loops, there must be a condition on the body that is invariant, and a terminating condition that changes. The terminating condition is written in the while clause. In this case, it is invariant (always true) that 2n−1< x. That means that the other part of our final condition is the part that changes.
assert x > 1
n= 1
while not ( x <= 2**n ): n= n + 1
assert 2**(n-1) < x assert 2**(n-1) < x <= 2**n
The next to last step is to show that when thewhilecondition is true, there are more than zero trips through the loop possible. We know thatxis finite and some power of 2 will satisfy this condition. There’s somen such thatn−1< log2x≤n, which limits the trips through the loop.
The final step is to show that each cycle through the loop reduces the trip count. We can argue that increasingngets us closer to the upper bound oflog2x.
TEN
FUNCTIONS
The heart of programming is theevaluate-applycycle, where function arguments are evaluated and then the function is applied to those argument values. We’ll review this inSemantics.InFunction Definition: The def and return Statementswe introduce the syntax for defining a function. In Function Use, we’ll describe using a function we’ve defined.
Some languages make distinctions between varieties of functions, separating them into “functions” and “subroutines”. We’ll visit this from a Python perspective inFunction Varieties.
We’ll look at some examples inSome Examples. We’ll look at ways to useIDLEin Hacking Mode.
We introduce some of the alternate argument forms available for handling optional and keyword parameters inMore Function Definition Features.
Further sophistication in how Python handles parameters has to be deferred toAdvanced Parameter Handling For Functions, as it depends on a knowledge of dictionaries, introduced inMappings and Dictionaries. InObject Method Functions we will describe how to use method functionsas a prelude toData Structures; real details on method functions are deferred untilClasses.
We’ll also defer examination of the yield statement until Iterators and Generators. The yield statement creates a special kind of function, one that is most useful when processing complex data structures, something we’ll look at inData Structures.
10.1 Semantics
A function, in a mathematical sense, is often described as a mapping from domain values to range values. Given a domain value, the function returns the matching range value.
If we think of the square root function, it maps a positive number,n, to another number,s, such thats2=n.
If we think of multplication as a function, it maps a pair of values, a and b, to a new value, c, such that
c=a×b. When we memorize multiplication tables, we are memorizing these mappings.
In Python, this narrow definition is somewhat relaxed. Python lets us create functions which do not need a domain value, but create new objects. It also allows us to have functions that don’t return values, but instead have some other effect, like reading user input, or creating a directory, or removing a file.
What We Provide. In Python, we create a new function by providing three pieces of information: the name of the function, a list of zero or more variables, called parameters, with the domain of input values, and a suite of statements that creates the output values. This definition is saved for later use. We’ll show this first inFunction Definition: The def and return Statements.
Typically, we create function definitions in script files because we don’t want to type them more than once. Almost universally, weimport a file with our function definitions so we can use them.
We use a function in an expression by following the function’s name with ‘()’. The Python interpreter evaluates the argument values in the ‘()’, then applies the function. We’ll show this second inFunction Use. Applying a function means that the interpreter first evaluates all of the argument values, then assigns the argument values to the function parameter variables, and finally evaluates the suite of statements that are the function’s body. In this body, any returnstatements define the resulting range value for the function. For more information on this evaluate-apply cycle, seeThe Evaluate-Apply Cycle.
Namespaces and Privacy. Note that the parameter variables used in the function definition, as well as any variables in a function are private to that function’s suite of statements. This is a consequence of the way Python puts all variables in a namespace. When a function is being evaluated, Python creates a temporary namespace. This namespace is deleted when the function’s processing is complete. The namespace associated with application of a function is different from the global namespace, and different from all other function-body namespaces.
While you can change the standard namespace policy (see The global Statement) it generally will do you more harm than good. A function’s interface is easiest to understand if it is only the parameters and return values and nothing more. If all other variables are local, they can be safely ignored.
Terminology: argument and parameter. We have to make a firm distinction between an argumentvalue, an object that is created or updated during execution, and the defined parametervariableof a function. The argument is the object used in particular application of a function; it may be referenced by other variables or objects. The parameter is a variable name that is part of the function, and is a local variable within the function body.
The Evaluate-Apply Cycle
The evaluate-apply cycle shows how any programming language computes the value of an expression. Consider the following expression:
math.sqrt( abs( b*b-4*a*c ) ) What does Python do?
For the purposes of analysis, we can restructure this from the various mathematical notation styles to a single, uniform notation. We call this prefix notation, because all of the operations prefix their operands. While useful for analysis, this is cumbersome to write for real programs.
math.sqrt( abs( sub( mul(b,b), mul(mul(4,a),c) ) ) )
We’ve replaced ‘x*y’ with ‘mul(x,y)’ , and replaced ‘x-y’ with ‘sub(x,y)’ . This allows us to more clearly see how evaluate-apply works. Each part of the expression is now written as a function with one or two arguments. First the arguments are evaluated, then the function is applied to those arguments. In order for Python to evaluate this ‘math.sqrt(...)’ expression, it evaluates the argument, ‘abs(...)’, and then appliesmath.sqrt()to it. This leads Python to a nested evaluate-apply process for the ‘abs(...)’ expression. We’ll show the whole process, with indentation to make it clearer. We’re going to show this as a list of steps, with ‘>’ to show how the various operations nest inside each other.
Evaluate the arg to math.sqrt: > Evaluate the args to sub: > > Evaluate the args to mul: > > > Get the value of b
> > Apply mul to b and b, creating r3=mul( b, b ). > > Evaluate the args to mul:
> > > Evaluate the args to mul: > > > > Get the value of a
> > > Apply mul to 4 and a, creating r5=mul( 4, a ). > > > Get the value of c
> > Apply mul to r5 and c, creating r4=mul( mul( 4, a ), c ).
> Apply sub to r3 and r4, creating r2=sub( mul( b, b ), mul( mul( 4, a ), c ) ).
Apply math.sqrt to r2, creating r1=math.sqrt( sub( mul( b, b ), mul( mul( 4, a ), c ) ) ). Notice that a number of intermediate results were created as part of this evaluation. If we were doing this by hand, we’d write these down as steps toward the final result.
The apply part of the evalute-apply cycle is sometimes termed a function call. The idea is that the main procedure “calls” the body of a function; the function does its work and returns to the main procedure. This is also called a functioninvocation.