main the Python way

Published

2023-07-31

main the Python way

So far, we’ve followed this general outline:

"""
A program which prompts the user for a radius of a circle, 
r, and calculates and reports the circumference.
"""

import math

def circumference(r_):
    return 2 * math.pi * r_

r = float(input('Enter a non-negative real number: '))
if r >= 0:
    c = circumference(r)
    print(f'The circumference of a circle of radius '
          f'{r:,.3f} is {c:,.3f}.')
else:
    print(f'I asked for a non-negative number, and '
          f'{r} is negative!')

This is conventional and follows good coding style (PEP 8).

You may have seen something like this:

"""
A program which prompts the user for a radius of a circle, 
r, and calculates and reports the circumference.
"""

import math

def circumference(r_):
    return 2 * math.pi * r_

def main():
    r = float(input('Enter a non-negative real number: '))
    if r >= 0:
        c = circumference(r)
        print(f'The circumference of a circle of radius '
              f'{r:,.3f} is {c:,.3f}.')
    else:
        print(f'I asked for a non-negative number, and '
              f'{r} is negative!')

main()

While this is not syntactically incorrect, it’s not really the Python way either.

Some textbooks use this, and there are abundant examples on the internet, perhaps attempting to make Python code look more similar to languages like C or Java (in the case of Java, an executable program must implement main()). But again, this is not the Python way.

Here’s how things work in Python. Python has what is called the top-level code environment. When a program is executed in this environment (which is what happens when you run your code within your IDE or from the command line), there’s a special variable __name__ which is automatically set to the value '__main__'.1 '__main__' is the name of the environment in which top-level code is run.

So if we wish to distinguish portions of our code which are automatically run when executed (sometimes called driver code) from other portions of our code (like imports and the functions we define), we do it thus:

"""
A program which prompts the user for a radius of a circle, 
r, and calculates and reports the circumference.
"""

import math 

def circumference(r_):
    return 2 * math.pi * r_

if __name__ == '__main__':
    
    # This code will only be executed if this module 
    # (program) is run. It will *not* be executed if 
    # this module is imported.
    
    r = float(input('Enter a non-negative real number: '))
    if r >= 0:
        c = circumference(r)
        print(f'The circumference of a circle of radius '
              f'{r:,.3f} is {c:,.3f}.')
    else:
        print(f'I asked for a non-negative number, and '
              f'{r} is negative!')

Let’s say we saved this file as circle.py. If we were to run this program from our IDE or from the command line with

$ python circle.py

Python would read the file, would see that we’re executing it, and thus would set __name__ equal to '__main__'. Then, after reading the definition of the function circumference(r_), it would reach the if statement,

if __name__ == '__main__':

This condition evaluates to True, and the code nested within this if statement would be executed. So it would prompt the user for a radius, and then check for valid input and return an appropriate response.

Another simple demonstration

Consider this Python program

"""
tlce.py (top-level code environment)
Another program to demonstrate the significance 
of __name__ and __main__.
"""

print(__name__)

if __name__ == '__main__':
    print("Hello World!")

Copy this code and save it as tlce.py (short for top-level code environment). Then, try running this program from within your IDE or from the command line. What will it print when you run it? It should print

__main__
Hello World!

So, you see, when we run a program in Python, Python sets the value of the variable __name__ to the string '__main__', and then, when the program performs the comparison __name__ == '__main__' this evaluates to True, and the code within the if is executed.

What happens if we import our module in another program?

Now write another program which imports this module (formally we refer to Python programs as modules).

In the same directory where you have tlce.py, create a new file

"""
A program which imports tlce (from the previous example).
"""

import tlce

Save this as use_tlce.py and then run it. What is printed? This program should print

tlce

So, if we import tlce then Python sets __name__ equal to 'tlce', and the body of the if is never executed.

Why would we do this? One reason is that we can write functions in one module, and import the module without executing any of the module’s code, but make the functions available to us. Sound familiar? It should. Consider what happens when we import the math module. Nothing is executed, but now we have math.pi, math.sqrt(), math.sin(), etc. available to us.

A complete example

Earlier we created a program which, given some radius, r, provided by the user, calculated the circumference, diameter, surface area, and volume of a sphere of radius r. Here it is, with some minor modifications, notably the addition of the check on the value of __name__.

"""
Sphere calculator (sphere.py)

Prompts the user for some radius, r, and then prints 
the circumference, diameter, surface area, and volume 
of a sphere with this radius.
"""

import math

def circumference(r_):
    return 2 * math.pi * r_

def diameter(r_):
    return 2 * r_

def surface_area(r_):
    return 4 * math.pi * r_ ** 2

def volume(r_):
    return 4 / 3 * math.pi * r_ ** 3

if __name__ == '__main__':
    r = float(input("Enter a radius >= 0.0: "))
    if r < 0:
        print("Invalid input")
    else:
        print(f"The diameter is "
              f"{diameter(r):0,.3f} units.")
        print(f"The circumference is "
              f"{circumference(r):0,.3f} units.")
        print(f"The surface area is "
              f"{surface_area(r):0,.3f} units squared.")
        print(f"The volume is "
              f"{volume(r):0,.3f} units cubed.")

Now we have a program that prompts the user for some radius, r, and uses some convenient functions to calculate these other values for a sphere. But it’s not a stretch to see that we might want to use these functions somewhere else!

Let’s say we’re manufacturing yoga balls—those inflatable balls that people use for certain exercises requiring balance. We’d want to know how much plastic we’d need to manufacture some number of balls. Say our yoga balls are 33 centimeters in radius when inflated, and that we want the thickness of the balls to be 0.1 centimeter.

In order to complete this calculation, we’ll need to calculate volume. Why reinvent the wheel? We’ve already written a function to do this!

Let’s import sphere.py and use the function provided by this module.

"""
Yoga ball material requirements
"""

import sphere 
# sphere.py must be in the same directory for this to work

RADIUS_CM = 33
THICKNESS_CM = 0.1
VINYL_G_PER_CC = 0.95
G_PER_KG = 1000

if __name__ == '__main__':
    balls = int(input("How many balls do you want "
                      "to manufacture this month? "))
    outer = sphere.volume(RADIUS_CM)
    inner = sphere.volume(RADIUS_CM - THICKNESS_CM)
    material_per_ball = outer - inner
    total_material = balls * material_per_ball
    total_material_by_weight 
            = total_material / VINYL_G_PER_CC / G_PER_KG

    print(f"To make {balls} balls, you will need "
          f"{total_material:,.1f} cc of vinyl.")
    print(f"Order at least "
          f"{total_material_by_weight:,.1f} "
          f"kg of vinyl to meet material requirements.")

See? We’ve imported sphere so we can use its functions. When we import sphere, __name__ (for sphere) takes on the value sphere so the code under if __name__ == '__main__' isn’t executed!

This allows us to have our cake (a program that calculates diameter, circumference, surface area, and volume of a sphere) and eat it too (by allowing imports and code reuse)! How cool is that?

What’s up with the funny names?

These funny names __name__ and '__main__' are called dunders. Dunder is short for double underscore. This is a naming convention that Python uses to set special variables, methods, and functions apart from the typical names programmers use for variables, methods, and functions they define.

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).

Footnotes

  1. Some other programming languages refer to the top-level as the entry point. '__main__' is the name of a Python program’s entry point.↩︎