[Tutor] [OT] Best Practices for Scientific Computing

Danny Yoo dyoo at hashcollision.org
Tue Dec 30 02:51:49 CET 2014


> When you have nearly identical or identical pieces of code, make functions
> rather than cut and paste the same code.  Call the function from various
> places. It makes it easier to bug fix later


We can make this more concrete.

Let's say that we are writing a program to do all kinds of dice rolls.
(I've been reading the 5th edition D&D Basic Rules lately, so that's
been on my mind.  :P)  Say that we want to roll two 6-sided dice and
print out the result of summing both dice rolls.


We might imagine a program like this:

###########################
import random

dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)

print("2d6: %d" % (dice1 + dice2))
###########################


Later, we might want another similar program that rolls four dice and
adds their result.  Well, that's easy enough: we can just copy and
paste.

#########################################
import random

dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)
dice3 = random.randint(1, 6)
dice4 = random.randint(1, 6)

print("4d6: %d" % (dice1 + dice2 + dice3 + dice4))
#########################################


Notice, though, that we needed to be a bit careful: we could just as
easily mis-typed something:

#########################################
import random

dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)
dice3 = random.randint(1, 6)
dice4 = random.randint(1, 6)

print("4d6: %d" % (dice1 + dice2 + dice3 + dice3))
#########################################

Whooops.


This is a contrived error, but several of the interesting bugs in
real-world code originate from errors during copy-and-paste.  We might
also say that this code isn't that flexible because a "minor" change
in the requirements (let's roll roll seven dice instead!) should be
reflected as a minor change in the code, and at the moment, it isn't:
we can see that we have to make multiple changes.


So let's fix that.  We can do so by generalizing the program, so that
it can handle a multitude of dice roles in a general way.  We can
design a function to do this.  Let's write a function, 'throwDice',
that takes a number of dice we want to throw, and returns their sum.


#########################################
import random

def throwDice(n):
    """Returns the sum of n d6 dice."""
    sum = 0
    for i in range(n):
        sum += random.randint(1, 6)
    return sum
#########################################


Once we have this function, now we can throw all sorts of dice, like:

    print("2d6: %d" % throwDice(2))

or

    print("4d6: %d" % throwDice(4))


Heck, we can start throwing dice like crazy:

#########################################
for i in [1, 2, 3, 4, 5]:
    print("%dd6: %d" % (i, throwDice(i)))
#########################################

(Visit http://repl.it/7Ix to see and run this program in your browser.)


This is what it means to use functions to avoid copy-and-paste: if we
find ourselves extending our program to do something more by copying
and pasting, where it starts to feel repetitive, then we should stop
and pause.  We might be able to generalize what we're doing as a
reusable function instead.  And then the repetition won't be in the
physical flow of our code, but rather that repetition will show up as
separate uses of that general function.


If you have more questions, please feel free to ask.


More information about the Tutor mailing list