Jump To
- Creating Functions In Python
- Functions That Return a Value
- Functions That Return Multiple Values
- Assigning Default Values to Arguments
- Calling Functions From Within Functions
- Variable Scope
- Putting It All Together: Dragon Battle
Creating Functions In Python
From the beginning, we have used many of the various functions that Python has to offer. We have covered basic input and output (input and print), mathematics (sqrt and sin), loops using range, and so on. But we are not limited to the functions defined in Python, or those in the standard modules included in most distributions. It is possible to write our own functions and reference them when necessary.
Functions are created using the following syntax.
def FUNCTION_NAME(ARGUMENTS): <CODE TO BE EXECUTED>
A function has two main components: the heading, and the body. The heading contains the def keyword, which tells Python that a new function is being defined. It also contains the name of the function, which uniquely identifies it within a program, and a list of arguments — variables that will be assigned values that are passed to the function. Like if statements and loops, the body is indented. It contains the code that is to be executed each time the function is run.
Say we wanted to write a function that displays the sum of n dice. We could write the following function to calculate and display the sum.
def sumDice(numDice):
sum = 0
for roll in range(numDice):
sum += random.randint(1,6)
print("The sum of the", numDice, "dice is", sum)
To execute code within a function, you must call the function using its name. For example, we could add the following lines to our program above to calculate the sums for 2 dice, 5 dice, and 100 dice.
sumDice(2) sumDice(5) sumDice(100)
Since our function relies on those in the random module, we must import it before the function is called. The full program, then, would look something like this.
# sumNdice.py
# J. Garvin
# 2011-11-04
# Calculates the sum of n dice.
import random
# Roll n dice
def sumDice(numDice):
sum = 0
for roll in range(numDice):
sum += random.randint(1,6)
print("The sum of the", numDice, "dice is", sum)
# Roll various numbers of dice
sumDice(2)
sumDice(5)
sumDice(100)
Anytime we wanted to roll any number of dice, we could simply call the sumDice function and supply it a specific number. While the function could be defined anywhere in the program, it is common practice to define all functions near the top of the program, and leave the “main” part of the program to the end. This is so that anyone who is reading the code does not have to search through the entire program trying to find where the function has been defined. It should be somwehere toward the beginning of the code.
Not all functions take arguments. For instance, the function below simply prints “Hello” to the screen.
def sayHello():
print("Hello")
Even though the function does not take any arguments, it is important to include the brackets in the function call. Issuing the following call produces the expected output.
sayHello()
On the other hand, the following commands seems to do nothing.
sayHello
Note that the program actually does something. It’s just that it does not do anything obvious. When a function’s name is referenced as above, Python determines the memory address of the function, but does not execute the code within it. This can be seen in IDLE by using a print statement.
print(sayHello) >>> <function sayHello at 0x171c9e0>
As another example, consider the problem of finding the number of diagonals in a convex polygon, such as a square or a hexagon. A diagonal is a line that connects two vertices through the interior of the polygon. As such, they are not adjacent (these would be edges, not diagonals). Since any two vertices in a triangle are adjacent, a triangle has zero diagonals. A rectangle has two diagonals, a pentagon (5 edges) five, and a hexagon (6 edges) nine. Try drawing them and see for yourself. Now, how many diagonals are in a hectogon, which has 100 edges?
To answer this question, consider the process of constructing a diagonal. First, we select a vertex, of which there are n possibilities. Next, we select a second vertex that is different from the first one, and is not adjacent. There are n-3 possibilities for the second vertex. Note, however, that the same diagonal can result from two different selections — we may select vertex 1 then vertex 5, or we may select vertex 5 then vertex 1. Thus, we have overcounted by a factor of two. Putting this together, we arrive at the formula for the number of diagonals, d, in an n-edged polygon, .
Here, then, is a function that calculates the number of diagonals in a polygon.
def countDiagonals(edges):
diagonals = edges * (edges - 3) / 2
print("A", edges, "edged polygon has", diagonals, "diagonals.")
countDiagonals(4)
countDiagonals(5)
countDiagonals(6)
countDiagonals(100)
Better to let Python calculate this than to try drawing it!
Exercises
- Write functions to perform each task, then call each function with the specified arguments.
- Determine if a given letter is one of those in the word PYTHON; P, N, A.
- Given an integer, 1-7, display the corresponding day of the week (e.g. “Monday”) or a suitable error message for an invalid value; 1, 5, 8.
- Calculate the sum of the first n positive integers; 5, 10, 50.
- Calculate the sum of the first n odd integers; 3, 10, 30.
- Calculate the measures of the angles in a triangle, given the lengths of the three sides, or output a suitable message if the triangle is impossible; 15, 8, 12.
Functions That Return a Value
In the previous section, we learned how to create functions in Python such that, when they were called, they performed one or more actions and displayed the result to the screen. Some functions do not display anything, but provide us with values that we can use in our programs as we see fit. Take, for example, the randint function from the random module.
import random num = random.randint(1,10) print(num)
The randint function does not display the randomly-selected integer — we must do so ourselves using print. Instead, randint selects a random integer and returns its value, which is then stored in num.
To return a value from a function, use the return keyword. The basic syntax is something like this.
def FUNCTION_NAME(AGUMENTS): <CODE TO BE EXECUTED> return VALUE
When calling the function, assign the returned value to a variable. A function call would look something like this.
VARIABLE = FUNCTION_NAME(ARGUMENTS)
For example, let’s write a function that computes the difference of two numbers. Our program may look like this.
def findDifference(num1, num2): difference = num1 - num2 return difference d = findDifference(7, 2) print(d)
The function receives two arguments, num1 and num2, which are given values 7 and 2 respectively. The difference is found, and a value of 5 is returned to the main program, where it is stored in variable d. We then display the value of d to check that the function has correctly calculated the difference.
Sometimes, we may wish to return different values if certain criteria are (not) met. For instance, let’s create a function that checks whether a given integer is even or odd. We can then call the function with two values, one of which is even, the other odd.
def isEven(num):
if num % 2 == 0:
return True
else:
return False
print(isEven(42))
print(isEven(17))
Inside of the function, the if and else statements handle the cases where the integer is divisible by two or not. Depending on the condition that is met, the function will return either True if the integer is even, or False if it is odd.
When the return keyword is read, the function returns to the point where it was called immediately. Any code after the return statement will not be executed. For instance, in the code below, nothing is ever printed to the screen because the print statement follows return.
def addNumbers(num1, num2):
sum = num1 + num2
return sum
print("The sum is", sum)
sum = addNumbers(3, 5)
Some programmers will exploit this feature, and write code such as the following.
def isEven(num):
if num % 2 == 0:
return True
return False
print(isEven(42))
print(isEven(17))
If the integer is divisible by two, the if statement will be true and the function will return True. If it is not divisible by two, the if statement will be False, and the code will resume after the if statement, returning False. While the code is shorter, it is not as clear as the previous example. For this reason, I advise against using this notation.
One of the major advantages of using functions is code reuse. In larger programs, it is very common to have sections of code that perform the same actions in various locations. It would be very cumbersome to have to enter this code several times, and if any canges had to be made in one section, then all sections might need updating as well. By using functions, however, it is possible to write code once, then call it multiple times with different values. Consider a simple game where two players each roll 5 dice. The player with the highest sum wins. Here is a program without any functions.
p1d1 = random.randint(1,6)
p1d2 = random.randint(1,6)
p1d3 = random.randint(1,6)
p1d4 = random.randint(1,6)
p1d5 = random.randint(1,6)
player1 = p1d1 + p1d2 + p1d3 + p1d4 + p1d5
p2d1 = random.randint(1,6)
p2d2 = random.randint(1,6)
p2d3 = random.randint(1,6)
p2d4 = random.randint(1,6)
p2d5 = random.randint(1,6)
player2 = p2d1 + p2d2 + p2d3 + p2d4 + p2d5
if player1 > player2:
print("Player 1 wins!")
else:
print("Player 2 wins!")
A better program would be to use a function that calculates the sum of five dice, then returns the value. This way, both players can be assigned scores by simply calling the function twice. Most people would argue that the main function is easier to read.
def sum5Dice():
d1 = random.randint(1,6)
d2 = random.randint(1,6)
d3 = random.randint(1,6)
d4 = random.randint(1,6)
d5 = random.randint(1,6)
sum = d1 + d2 + d3 + d4 + d5
return sum
player1 = sum5Dice()
player2 = sum5Dice()
if player1 > player2:
print("Player 1 wins!")
else:
print("Player 2 wins!")
Of course, we might clean up our code within the function by using a loop. This gives us our final program.
def sum5Dice():
sum = 0
for roll in range(5):
sum += random.randint(1,6)
return sum
player1 = sum5Dice()
player2 = sum5Dice()
if player1 > player2:
print("Player 1 wins!")
else:
print("Player 2 wins!")
Note: all of the variations above have the same logical error in them. Can you figure it out?
Exercises
- Write functions to solve each task, returning a value to your main program each time.
- Calculate the area of a circle, given its radius.
- Calculate the surface area of a cylinder, given its height and radius.
- Calculate the volume of a rectangular prism, given its height, width and depth.
- Determine the time for an object, thrown with velocity v from an initial height hi, to hit the ground. Recall that the height of an object, after t seconds, can be found using the formula
.
- Determine the greatest common factor (GCF) of two integers, m and n. For example, the GCF of 12 and 8 is 4.
- Determine the lowest common multiple (LCM) of two integers, m and n. For example, the LCM of 9 and 6 is 18.
Functions That Return Multiple Values
Exercises
- Write functions that solve each task. Your functions should return all values to your main program.
- Write a function getNames that prompts the user to enter their first and last names. Print the user’s full name from your main program.
- Write a function quotRem that returns the quotient and remainder when n1 is divided by n2.
- Write a function roots that calculates the real roots of a quadratic equation in standard form.
- Write a function meanMedian which computes the mean and median of five distinct integers.
- Write a function slope that calculates the slope between two points and returns the numerator and denominator.
- Write a function eqnLine that calculates the equation of a line, given its slope and a point on the line.
- Write a program that determines the location of a point that is equidistant from three others. Hint: use the two functions developed above.
Putting It All Together: Dragon Battle
Let’s say we want to write a “game” (in the loose sense of the word) in which two dragons fight to the death. While this game certainly won’t be on the same level as Dragon Age or Skyrim, it will have some of the same elements at heart. Each dragon will have a certain number of “hit points” (a value that, when positive, indicates that the dragon is still alive), an “attack” score (how much damage it can inflict), and a “defence” score (how much damage taken is reduced). The two dragons are not controllable, but there is no reason why you cannot implement this functionality with minimal additions and changes.
Like most RPG-style games, the formulae used for determining damage or for testing whether an attack was successful are largely arbitrary. Developers extensively test their programs to find the right balance that makes playing the game enjoyable. In the program we will develop, we will use very simple formula to make decisions — this is to illustrate the main ideas, rather than to fully flesh out the fine details.
We begin by defining a function that will generate stats for two dragons. A simple way to do this is to use random numbers for each attribute. Our function, then, might look something like this.
def assignStats():
HP = random.randint(25,50)
attack = random.randint(1,5)
defence = random.randint(1,5)
return HP, attack, defence
Next, we need to define another function that will determine which of the two dragons successfully wins the attack. To do this, use the following rules:
- Given two dragons’ attack values m and n, generate m random integers between 1 and 10 for dragon 1, and n random integers between 1 and 10 for dragon 2.
- The winner of the attack is the dragon that has the largest randomly-generated integer.
- In the case of a tie, there is no winner.
Again, this is completely arbitrary, and you may wish to find a better set of rules that you find work better as a game. It should be obvious that the dragon with the higher attack score has an advantage over the first dragon, since it has more opportunities to generate a larger number. A function that does this, and returns the winner of the battle, is below.
def attack(p1attack, p2attack):
p1high = highestRandom(p1attack)
p2high = highestRandom(p2attack)
if p1high > p2high:
print("Dragon 1 wins the attack,", p1high, "-", p2high)
return 1
elif p2high > p1high:
print("Dragon 2 wins the attack,", p2high, "-", p1high)
return 2
else:
print("Tie! No damage done this round.")
return 0
Note the function call to highestRandom inside of our attack function. The highestRandom function is defined as follows, and returns the largest of n random integers between 1 and 10.
def highestRandom(n):
high = 0
for count in range(n):
num = random.randint(1,10)
if num > high:
high = num
return high
In the case of a tie, we return a zero to the main program, since there was no winner. The choice of value here is up to you, but zero is usually a good bet, and easy to remember.
Next, we need a function that will inflict damage on the loser. The amount of damage inflicted should depend on two things: the attacking dragon’s attack score, and the defending dragon’s defence score. Since we want to use this function in two cases — either dragon 1 attacks, or dragon 2 does — it is best to leave the argument names general. The code below uses a simple calculation to inflict damage.
def getDamage(attack, defence):
damage = 4 * attack - ((2 * defence) // 3)
return damage
It is impossible to have an invincible dragon using the calculation above. In the best case, the attacker has an attack score of 5 and the defender has a defence score of 1, resulting in damage of 20 points. In the worst case, the attacker has an attack score of 1 and the defender has a defence score of 5, resulting in damage of 1.
The main program must do several things. First, it must create the two dragons.
p1HP, p1attack, p1defence = assignStats()
p2HP, p2attack, p2defence = assignStats()
print("Dragon 1 has",p1HP,"HP,",p1attack,"attack and",p1defence,"defence.")
print("Dragon 2 has",p2HP,"HP,",p2attack,"attack and",p2defence,"defence.")
Next, it must initiate a game loop, whereby the two dragons fight until one dragon’s hit points drops to zero or below. A loop header might look like this.
while p1HP > 0 and p2HP > 0:
...
Inside of the loop, the dragons repeatedly attack. After each attack, the appropriate damage is subtracted from the loser’s hit points, and a message is output indicating the current status of each dragon.
winner = attack(p1attack, p2attack)
if winner == 1:
damage = getDamage(p1attack, p2defence)
print("Dragon 1 does",damage,"HP damage to dragon 2.")
p2HP -= damage
elif winner == 2:
damage = getDamage(p2attack, p1defence)
print("Dragon 2 does",damage,"HP damage to dragon 1.")
p1HP -= damage
print("Dragon 1 has",p1HP,"HP and dragon 2 has",p2HP,"HP.")
At some point, one of the dragons will die. When this occurs, we output a message identifying the winning dragon, and the game ends.
if p1HP < 0:
print("Dragon 2 wins!")
else:
print("Dragon 1 wins!")
Putting it all together, with some appropriate comments and headers, the final game looks like this.
# DRAGON_BATTLE.PY
# J. Garvin
# 2011-11-18
# Makes two dragons fight to the death.
import random
# Generate random stats for each dragon
def assignStats():
HP = random.randint(25,50)
attack = random.randint(1,5)
defence = random.randint(1,5)
return HP, attack, defence
# Generates n random integers 1-10 and returns the highest
def highestRandom(n):
high = 0
for count in range(n):
num = random.randint(1,10)
if num > high:
high = num
return high
# Generate n random integers (1-10) based on each dragon's
# attack value n. The winner is the dragon with the largest
# randomly-generated integer.
def attack(p1attack, p2attack):
# generate values for dragons
p1high = highestRandom(p1attack)
p2high = highestRandom(p2attack)
# determine, and output, the winner of the attack
if p1high > p2high:
print("Dragon 1 wins the attack,", p1high, "-", p2high)
return 1
elif p2high > p1high:
print("Dragon 2 wins the attack,", p2high, "-", p1high)
return 2
else:
print("Tie! No damage done this round.")
return 0
# Inflict damage on another dragon
def getDamage(attack, defence):
damage = 4 * attack - ((2 * defence) // 3)
return damage
# Create the dragons
p1HP, p1attack, p1defence = assignStats()
p2HP, p2attack, p2defence = assignStats()
print("Player 1's dragon has",p1HP,"HP, ",p1attack,"attack and",p1defence,"defence.")
print("Player 2's dragon has",p2HP,"HP, ",p2attack,"attack and",p2defence,"defence.")
# Loop until a dragon is dead (0 HP or fewer)
while p1HP > 0 and p2HP > 0:
# Winning player inflicts damage on loser
winner = attack(p1attack, p2attack)
if winner == 1:
damage = getDamage(p1attack, p2defence)
print("Player 1 does",damage,"HP damage to player 2.")
p2HP -= damage
elif winner == 2:
damage = getDamage(p2attack, p1defence)
print("Player 2 does",damage,"HP damage to player 1.")
p1HP -= damage
# Display current HP for each dragon
print("Player 1 has",p1HP,"HP and player 2 has",p2HP,"HP.")
# Display the winner
if p1HP < 0:
print("Player 2 wins!")
else:
print("Player 1 wins!")
Exercises
- Modify the program to suit your needs. You may wish to add other attributes (speed, agility, etc.) and factor them into your calculations. You may introduce special attacks (breathing fire or ice, bites versus claw-based attacks, etc.) that inflict different amounts of damage. You may wish to make a player determine the actions (attack, defend, run) and write some artificial intelligence for a computer player to challenge them. Be creative!
for 1E, do you mean a right angled triangle
Nope. Keep it general. What tool can you use that will work for all triangles?