Creating functions in Python (Part 4)

Global and local reuse

We continue our discussion of the reuse of names by considering what happens if we use a name in both a permanent address book and a temporary address book.

The variable “iguana” is placed in the permanent address book with value 10.
iguana = 10

def big(num):
    iguana = num + 3
    return iguana

We enter iguana in the permanent address book as a variable with value 10 and enter big in the permanent address book as the name of a function.

The variable “iguana” is placed in the permanent address book with value 10; the function “big” is also placed in the permanent address book.
iguana = 10

def big(num):
    iguana = num + 3
    return iguana

print(iguana)

When we ask the computer to print "iguana", print(iguana), the function big is not used, and hence no temporary address book is created. The value printed will be 10.

Calling print(iguana) outputs 10 since the function big is not used and no temporary address book is created. Calling print(big(5)) outputs 8 since a temporary address book is created with parameter num = 5 and iguana = num + 3.
iguana = 10

def big(num):
    iguana = num + 3
    return iguana

print(iguana)
print(big(5))

If we call big(5), a temporary address book is created, with parameter num having value 5 and variable iguana having value num + 3, or 8. The value printed will be 8.

Calling print(big(iguana)) outputs 13 since a temporary address book is created with parameter num being assigned to the value stored with guana in the permanent address book. Now the temporary address book will create a local variable iguana = num + 3.
iguana = 10

def big(num):
    iguana = num + 3
    return iguana

print(iguana)
print(big(5))
print(big(iguana))

If we call big(iguana), a temporary address book is created, with parameter name num being assigned to the value stored with the name iguana in the permanent address book, and now the temporary address book will create a local variable iguana with value num + 3, or 13. This is the value that will be printed.

Consider the example code

def jackal(num):
    return num

def large(age):
    jackal = age + 3
    return jackal

print(jackal(10))
print(large(10))

which gives the output

10
13

In this example, jackal is the name of a function and also the name of a local variable in another function. When we call jackal(10), we are using the permanent address book to find the name of the function. This results in the value 10 being printed. If we call the function large(10), then in the temporary address book, the parameter age has value 10, and the local variable jackal has value 10 + 3, or 13. This results in the value 13 being printed. What if we call large(jackal)? Running the code

def jackal(num):
    return num

def large(age):
    jackal = age + 3
    return jackal

print(jackal(10))
print(large(10))
print(large(jackal))

gives the output

10
13
Traceback (most recent call last):
  File "<string>", line 10, in <module>
  File "<string>", line 5, in large
TypeError: unsupported operand type(s) for +: 'function' and 'int'

What we are trying to do is to use the function jackal as input to the function large. The error message “unsupported operand type for + ” indicates that we ran into trouble when trying to add age, which is the function jackal, to the number 3. The error was a consequence of what we were trying to do, namely add a function and a number, not the use of the name both globally and locally.

Observation

The same name can be used for a global variable and a local variable or parameter.

Observation

The same name can be used for a function name and a local variable or parameter.

Good habit

When it affects clarity, reusing names should be avoided.

To conclude, we can use the same name in both permanent and temporary address books, though we should keep in mind that just because we can do something doesn't mean we should do it. If it is clearer to use different names, use different names.


Example: gold cost for a ring

A right circular cylindrical ring showing the inner radius, outer radius, thickness, and width.

With all this in mind, let's create a function that determines the cost of a gold ring using the

  • circumference of the finger,
  • thickness of the ring,
  • width of the ring, and
  • cost of gold per cubic mm.

We'll use a constant for the cost of a cubic millimeter of gold and use as parameters the finger circumference, width of the ring, and thickness of the ring. We can think of the ring as a cylinder with smaller cylinder removed. So most of our work is in finding the volume of the two cylinders. We know how to compute the volume of a cylinder given the area of the circle at one end and the length. Since the length here is the width of the ring, our tasks are as follows:

  • Determine the inner radius.
  • Determine the outer radius.
  • Determine the volumes.
  • Compute the total volume from the two volumes.
  • Calculate the cost of the ring.

Following the recipe

Assuming that the cost of gold is $0.8098 per cubic mm.

import math

COST_MM3 = .8098 # cost per cubic mm

Since we're likely to use π, we'll start by importing the math module and defining our constant.


def area_circle(radius):
    return math.pi * radius ** 2

def volume_cylinder(radius, length):
    return length * area_circle(radius)

We'll define some helper functions to help compute the volume of the cylinder. I've chosen to use the radius as a parameter for both functions to illustrate that the same name can be used for both.


def circ_to_radius(circ)

Since determining radius from circumference is a task that might be useful in other programs, we create a helper function. To create the header of the helper function, we need a name for the function and a name for the parameter. I am reusing the name circ which is also the name of a parameter for ring_cost.


    return circ / (2 * math.pi)

Since the circumference of a circle is 2 × π × the radius, to get the radius we divide the circumference by (2 × π).


def ring_cost(circ, width, thickness):

We add the function header. Let's call the function ring_cost and our three parameters circ for circumference, width, and thickness. We don't have the inner radius, just the circumference.


    ## Determine inner radius
    radius = circ_to_radius(circ)

We now complete the first task. Notice that I'm reusing the name radius, which already appears as a parameter name in both area_circle and volume_cylinder. When we call ring_cost, the input for finger circumference will have the local name circ. When circ_to_radius is called, the value will then also be assigned to the local name circ in that function call, with no interference.

    ## Determine outer radius
    outer_radius = radius + thickness

The outer radius of the circle is the radius of the finger plus the thickness of the ring.


    ## Determine volumes
    inner_volume = volume_cylinder(radius, width)
    outer_volume = volume_cylinder(outer_radius, width)

We can now use our helper function to determine the volumes of the cylinders. Notice that radius is both a local variable in ring_cost and the parameter in volume_cylinder. Again, there will be no interference.


    ## Compute total volume
    total_volume = outer_volume - inner_volume
    ## Calculate cost
    return total_volume * COST_MM3

Computing the total volume is easy, as is calcuating the total cost.


print(ring_cost(57, 2, 2))
print(ring_cost(10, 5, 3))

Finally, we can verify that the function works correctly.


Putting them all together

Here is the complete program when all parts are put together:

import math

COST_MM3 = .8098 # cost per cubic mm

def area_circle(radius):
    return math.pi * radius ** 2

def volume_cylinder(radius, length):
    return length * area_circle(radius)

def circ_to_radius(circ):
    return circ / (2 * math.pi)

def ring_cost(circ, width, thickness):
    ## Determine inner radius
    radius = circ_to_radius(circ)

    ## Determine outer radius
    outer_radius = radius + thickness

    ## Determine volumes
    inner_volume = volume_cylinder(radius, width)
    outer_volume = volume_cylinder(outer_radius, width)
 
    ## Compute total volume
    total_volume = outer_volume - inner_volume

    ## Calculate cost
    return total_volume * COST_MM3

print(ring_cost(57, 2, 2))
print(ring_cost(10, 5, 3))

Running the program gives the output

204.98689384701612
235.95277788946566

This confirms that our program works correctly.