John | 曲

Reflection in Transition

04 FUNCTIONS, SCOPING, AND ABSTRACTION

John Qu / 2020-05-26


What is the disadvantage of a single piece of code?

x = 25
epsilon = 0.01
numGuesses = 0
low = 0.0
high = max(1.0, x)
ans = (high + low)/2.0
while abs(ans**2 - x) >= epsilon:
    print('low =', low, 'high =', high, 'ans =', ans) 
    numGuesses += 1
    if ans**2 < x:
        low = ans 
    else:
        high = ans
    ans = (high + low)/2.0
## low = 0.0 high = 25 ans = 12.5
## low = 0.0 high = 12.5 ans = 6.25
## low = 0.0 high = 6.25 ans = 3.125
## low = 3.125 high = 6.25 ans = 4.6875
## low = 4.6875 high = 6.25 ans = 5.46875
## low = 4.6875 high = 5.46875 ans = 5.078125
## low = 4.6875 high = 5.078125 ans = 4.8828125
## low = 4.8828125 high = 5.078125 ans = 4.98046875
## low = 4.98046875 high = 5.078125 ans = 5.029296875
## low = 4.98046875 high = 5.029296875 ans = 5.0048828125
## low = 4.98046875 high = 5.0048828125 ans = 4.99267578125
## low = 4.99267578125 high = 5.0048828125 ans = 4.998779296875
## low = 4.998779296875 high = 5.0048828125 ans = 5.0018310546875
print('numGuesses =', numGuesses) 
## numGuesses = 13
print(ans, 'is close to square root of', x)
## 5.00030517578125 is close to square root of 25

This is a reasonable piece of code, but it lacks general utility. There are two main reasons.

  1. depending on inner variable value hard to reuse;
  2. Multiple chunks of almost identical code.

It works only for values denoted by the variables x and epsilon. This means that if we want to reuse it, we need to copy the code, possibly edit the variable names, and paste it where we want it. Because of this we cannot easily use this computation inside of some other, more complex, computation.

Furthermore, if we want to compute cube roots rather than square roots, we have to edit the code. If we want a program that computes both square and cube roots (or for that matter square roots in two different places), the program would contain multiple chunks of almost identical code. This is a very bad thing. The more code a program contains, the more chance there is for something to go wrong, and the harder the code is to maintain. Imagine, for example, that there was an error in the initial implementation of square root, and that the error came to light when testing the program. It would be all too easy to fix the implementation of square root in one place and forget that there was similar code elsewhere that was also in need of repair.

4.1 Functions and Scoping

What is the professional terms about a function?

  • function name: simply a name that is used to refer to the function

  • formal parameters:
  • actual parameters (often referred to as arguments):
  • function invocation (also referred to as a function call) > The sequence of names within the parentheses following the function name (x, y in this example) are the formal parameters of the function. When the function is used, the formal parameters are bound (as in an assignment statement) to the actual parameters (often referred to as arguments) of the function invocation (also referred to as a function call).

    A function call is an expression, and like all expressions it has a value. That value is the value returned by the invoked function.

  • function body:

    The function body is any piece of Python code. this notion of function is more general than what mathematicians call a function. It was first popularized by the programming language Fortran 2 in the late 1950s.

  • point of execution: the next instruction to be executed

  • lambda abstraction:

    Parameters provide something called lambda abstraction, allowing programmers to write code that manipulates not specific objects, but instead whatever objects the caller of the function chooses to use as actual parameters. 【不理解这里的意思,不理解为什么放在这里说。】

  • name space (also called a scope):

    The formal parameter x and the local variable y that are used in f exist only within the scope of the definition of f.

  • symbol table: keeps track of all names defined at that level and their current bindings.

  • a stack frame: a new symbol table created when a function is called

    This table keeps track of all names defined within the function (including the formal parameters) and their current bindings. If a function is called from within the function body, yet another stack frame is created.

  • static or lexical scoping: In Python, one can always determine the scope of a name by looking at the program text.

4.2 Specifications

What are the benefits of the investment in writing testing code?

  1. It beats typing test cases into the shell over and over again during debugging.
  2. It forces us think about which tests are likely to be more illuminating.

What is docstring in Python used for?

By convention, Python programmers use docstrings to provide specifications of functions. These docstrings can be accessed using the built-in function help.

What is specification of a function?

A specification of a function defines a contract between the implementer of a function and those who will be writing programs that use the function. We will refer to the users of a function as its clients. This contract can be thought of as containing two parts:

• Assumptions: These describe conditions that must be met by clients of the function. Typically, they describe constraints on the actual parameters. Almost always, they specify the acceptable set of types for each parameter, and not infrequently some constraints on the value of one or more of the parameters. For example, the first two lines of the docstring of findRoot describe the assumptions that must be satisfied by clients of findRoot.

• Guarantees: These describe conditions that must be met by the function, provided that it has been called in a way that satisfies the assumptions. The last two lines of the docstring of findRoot describe the guarantees that the implementation of the function must meet.

findRoot(x, power, epsilon)
    Assumes x and epsilon int or float, power an int,
        epsilon > 0 & power >= 1
    Returns float y such that y**power is within epsilon of x.
        If such a float does not exist, it returns None

What is the meaning of creating functions in programming?

Functions are a way of creating computational elements that we can think of as primitives.

By what means does function facilitate the convenience of have handy primitives?

Functions facilitate this by providing decomposition and abstraction.

Decomposition creates structure. It allows us to break a program into parts that are reasonably self-contained, and that may be reused in different settings.

Abstraction hides detail. It allows us to use a piece of code as if it were a black box—that is, something whose interior details we cannot see, don’t need to see, and shouldn’t even want to see.1

What is the essence of abstraction?

The essence of abstraction is preserving information that is relevant in a given context, and forgetting information that is irrelevant in that context.

How to use abstraction effectively in programming?

The key to using abstraction effectively in programming is finding a notion of relevance that is appropriate for both the builder of an abstraction and the potential clients of the abstraction. That is the true art of programming.

What is abstraction all about? Give some model in daily life.

Abstraction is all about forgetting. There are lots of ways to model this, for example, the auditory apparatus of most teenagers.

Teenager says: May I borrow the car tonight?

Parent says: Yes, but be back before midnight, and make sure that the gas tank is full.

Teenager hears: Yes.

The teenager has ignored all of those pesky details that he or she considers irrelevant. Abstraction is a many-to-one process. Had the parent said Yes, but be back before 2:00 a.m., and make sure that the car is clean, it would also have been abstracted to Yes.

What is the role of specification of module in team programming?

This is the way organizations go about using teams of programmers to get things done. Given a specification of a module, a programmer can work on implementing that module without worrying unduly about what the other programmers on the team are doing. Moreover, the other programmers can use the specification to start writing code that uses that module without worrying unduly about how that module is to be implemented.

4.3 Recursion

Why it is a charming urban legend that recursion is a rather subtle programming technique?

Recursion is a very important idea, but it’s not so subtle, and it is more than a programming technique.

What kind of method is recursive?

It is a descriptive method.

What is a recursive description made of?

In general, a recursive definition is made up of two parts. There is at least one base case that directly specifies the result for a special case (case 1 in the example above), and there is at least one recursive (inductive) case (cases 2 and 3 in the example above) that defines the answer in terms of the answer to the question on some other input, typically a simpler version of the same problem.

Why we should be explicit when speaking “nature number”?

The exact definition of “natural number” is subject to debate. Some define it as the positive integers and others as the nonnegative integers. That’s why we were explicit about the possible values of n in the docstrings in

"""Assumes n an int > 0
   Returns n!"""

What are the iterative and recursive implementation of factorial?

The classic inductive definition is \[ \begin{array}{c}1 !=1 \\ (n+1) !=(n+1) * n !\end{array} \] Here is the iterative implementation of factorial.

def factI(n):
    """ Assumes n an int > 0
        Returns n!""" 
    result = 1
    while n > 1:
        result = result * n 
        n -= 1
    return result

Here is the recursive implementation of factorial.

def factR(n):
    """ Assumes n an int > 0
        Returns n!""" 
    if n == 1:
        return n
    else:
        return n * factR (n - 1)

The second is a more obvious translation of the original recursive definition.

How to describe the Fibonacci sequence of rabbits?

Suppose a newly born pair of rabbits, one male and one female, are put in a pen (or worse, released in the wild). Suppose further that the rabbits are able to mate at the age of one month (which, astonishingly, some breeds can) and have a one-month gestation period (which, astonishingly, some breeds do). Finally, suppose that these mythical rabbits never die, and that the female always produces one new pair (one male, one female) every month from its second month on. How many female rabbits will there be at the end of six months?

On the last day of the first month (call it month 0), there will be one female (ready to conceive on the first day of the next month). On the last day of the second month, there will still be only one female (since she will not give birth until the first day of the next month). On the last day of the next month, there will be two females (one pregnant and one not). On the last day of the next month, there will be three females (two pregnant and one not). And so on. Let’s look at this progression in tabular form, Figure 4.6.

Month Females
0 1
1 1
2 2
3 3
4 5
5 8
6 13

Figure 4.6 Growth in population of female rabbits

Notice that for month n > 1, females(n) = females(n-1) + females(n-2). This is not an accident. Each female that was alive in month n-1 will still be alive in month n. In addition, each female that was alive in month n-2 will produce one new female in month n. The new females can be added to the females alive in month n-1 to get the number of females in month n.

The growth in population is described naturally by the recurrence.

females(0) = 1
females(1) = 1
females(n + 2) = females(n+1) + females(n)

Here is an obviously correct, but terribly inefficient implementation of the Fibonacci function.

def fib(n):
    """ Assues n int >= 0
        Returns Fibonacci of n """
    if n == 0 or n == 1:
        return 1
    else:
            return fib(n-1) + fib(n-2)

def testFib(n):
    for i in range(n+1):
            print('fib of', i, '=', fib(i))

testFib(8)
## fib of 0 = 1
## fib of 1 = 1
## fib of 2 = 2
## fib of 3 = 3
## fib of 4 = 5
## fib of 5 = 8
## fib of 6 = 13
## fib of 7 = 21
## fib of 8 = 34

How do you feel in the coding process of the Fibonacci function?

Writing the code is the easy part of solving this problem. Once we went from the vague statement of a problem about bunnies to a set of recursive equations, the code almost wrote itself. Finding some kind of abstract way to express a solution to the problem at hand is very often the hardest step in building a useful program.

How to construct code to implement palindrome testing with printed message to visualize the processing steps?

[Note where the printed messages are put.]

def isPalindrome(s): 
    """ Assumes s is a str
        Returns True if s is a palindrome; False otherwise. 
        Punctuation marks, blanks, and capitalization are ignored."""
        
    def toChars(s): 
        s = s.lower() 
        letters = '' 
        for c in s:
            if c in 'abcdefghijklmnopqrstuvwxyz': 
                letters = letters + c
        return letters
        
    def isPal(s):
        print(' isPal called with', s) 
        if len(s) <= 1:
            print(' About to return True from base case')
            return True
        else:
            answer = s[0] == s[-1] and isPal(s[1:-1]) 
            print(' About to return', answer, 'for', s) 
            return answer
            
    return isPal(toChars(s))
      
def testIsPalindrome(): 
    print('Try dogGod') 
    print(isPalindrome('dogGod')) 
    print('Try doGood') 
    print(isPalindrome('doGood'))
    
testIsPalindrome()
## Try dogGod
##  isPal called with doggod
##  isPal called with oggo
##  isPal called with gg
##  isPal called with 
##  About to return True from base case
##  About to return True for gg
##  About to return True for oggo
##  About to return True for doggod
## True
## Try doGood
##  isPal called with dogood
##  isPal called with ogoo
##  isPal called with go
##  About to return False for go
##  About to return False for ogoo
##  About to return False for dogood
## False

What is short-circuit evaluation?

The conjunction2 in the else clause is evaluated from left to right. The code first checks whether the first and last characters are the same, and if they are goes on to check whether the string minus those two characters is a palindrome. That the second conjunct is not evaluated unless the first conjunct evaluates to True is semantically irrelevant in this example. However, later in the book we will see examples where this kind of short-circuit evaluation of Boolean expressions is semantically relevant.

What is the problem-solving principle known as divide-and-conquer?

This principle is related to but slightly different from divide-and-conquer algorithms.

The problem-solving principle is to conquer a hard problem by breaking it into a set of subproblems with the properties that

  • the subproblems are easier to solve than the original problem, and
  • solutions of the subproblems can be combined to solve the original problem.

Divide-and-conquer is a very old idea. Julius Caesar practiced what the Romans referred to as divide et impera (divide and rule). The British practiced it brilliantly to control the Indian subcontinent. Benjamin Franklin was well aware of the British expertise in using this technique, prompting him to say at the signing of the U.S. Declaration of Independence, “We must all hang together, or assuredly3 we shall all hang separately.”

4.4 Global Variables

Why the sloppy use of global variable can destroy programs readability?

It is with some trepidation4 that we introduce the topic of global variables. Since the 1970s card-carrying5 computer scientists have inveighed6 against them. The indiscriminate7 use of global variables can lead to lots of problems. The key to making programs readable is locality8. One reads a program a piece at a time, and the less context9 needed to understand each piece, the better. Since global variables can be modified or read in a wide variety of places, the sloppy use of them can destroy locality. Nevertheless, there are times when they are just what is needed.

4.5 Modules

What is Python module?

Python modules allow us to easily construct a program from code in multiple files. A module is a .py file containing Python definitions and statements.

How to use names in Module?

Modules are typically stored in individual files. Each module has its own private symbol table. Executing import M creates a binding for module Min the scope in which the import appears. Therefore, in the importing context we use dot notation to indicate that we are referring to a name defined in the imported module.10

Why some Python programmers frown upon using the from modele import * form of import?

Because it allows the importing program to omit the module name when accessing names defined inside the imported module. Executing the statement from M import * creates bindings in the current scope to all objects defined within M, but not to M itself. They believe that it makes code more difficult to read.

When is the module imported?

As we have seen, a module can contain executable statements as well as function definitions. Typically, these statements are used to initialize the module. For this reason, the statements in a module are executed only the first time a module is imported into a program. Moreover, a module is imported only once per interpreter session. If you start up a shell, import a module, and then change the contents of that module, the interpreter will still be using the original version of the module. This can lead to puzzling behavior when debugging.

4.6 Files

How does Python achieves operation-system independence?

Accessing files through something called a file handle.

Why is it important to remember to close the file when the program is finished using it?

There is a risk that some or all of the writes may not be saved.

Some of the common operations on files.

open(fn, ‘w’) fn is a string representing a file name. Creates a file for writing and returns a file handle.

open(fn, ‘r’) fn is a string representing a file name. Opens an existing file for reading and returns a file handle.

open(fn, ‘a’) fn is a string representing a file name. Opens an existing file for appending and returns a file handle.

fh.read() returns a string containing the contents of the file associated with the file handle fh.

fh.readline() returns the next line in the file associated with the file handle fh.

fh.readlines() returns a list each element of which is one line of the file asso- ciated with the file handle fh.

fh.write(s) writes the string s to the end of the file associated with the file handle fh.

fh.writeLines(S) S is a sequence of strings. Writes each element of S as a sepa- rate line to the file associated with the file handle fh.

fh.close() closes the file associated with the file handle fh.


  1. “Where ignorance is bliss, ’tis folly to be wise.”—Thomas Gray “无知是福,大智若愚。

  2. When two Boolean-valued expressions are connected by “and,” each expression is called a conjunct. If they are connected by “or,” they are called disjuncts.

  3. [as sentence adverb] used to express the speaker’s certainty that something is true: potted roses will most assuredly not survive winter without protection.

  4. trepidation: An involuntary trembling, sometimes an effect of paralysis, but usually caused by terror or fear; quaking; quivering. Hence, a state of terror or alarm; fear; confusion; fright; as, the men were in great trepidation.

  5. card-carrying: often humorous confirmed in or dedicated to a specified pursuit or outlook: a card-carrying pessimist.

  6. inveigh To declaim or rail (against some person or thing); to utter censorious and bitter language; to attack with harsh criticism or reproach, either spoken or written; to use invectives; – with against; as, to inveigh against character, conduct, manners, customs, morals, a law, an abuse.

  7. Inˊdis-crim′i-nate, a. Not discriminate; wanting discrimination; undistinguishing; not making any distinction; confused; promiscuous. “Blind or indiscriminate forgiveness.”

  8. locality:Limitation to a county, district, or place; as, locality of trial.

  9. The part or parts of something written or printed, as of Scripture, which precede or follow a text or quoted sentence, or are so intimately associated with it as to throw light upon its meaning. According to all the light that the contexts afford. - Sharp.

  10. Superficially, this may seem unrelated to the use of dot notation in method invocation. However, as we will see in Chapter 8, there is a deep connection.