How to Use the Python Main Function with Arguments

To use the Python main function with arguments, define main() with a parameter for the arguments, then call it under if __name__ == "__main__": and pass it sys.argv[1:], the words after the script name. Its return value goes to sys.exit() as the exit code:

import sys

def main(args):
    print("You passed:", args)
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

I ran each script here on Python 3.12.5 in a Windows Command Prompt, with different arguments each time, and pasted the real output under each one.

In this tutorial, we’ll build that pattern up step by step: the __name__ guard, reading raw arguments, switching to argparse, the testable main(argv=None) form, and exit codes.

What does if __name__ == “__main__” do for a main function?

Python has no built-in main() like C or Java. It’s just a normal function name. What makes it the entry point is the guard at the bottom of the file, which checks how the file was started.

When you run a file directly, Python sets its __name__ variable to "__main__". When another file imports it, __name__ is the module’s name instead. This tiny file prints which case it’s in:

print(f"helpers.py sees __name__ = {__name__!r}")

def main():
    print("helpers.main() ran")

if __name__ == "__main__":
    main()

Output:

C:\pyguides> python helpers.py
helpers.py sees __name__ = '__main__'
helpers.main() ran
C:\pyguides> python -c "import helpers"
helpers.py sees __name__ = 'helpers'
Command Prompt showing a Python file printing __name__ as __main__ when run and as helpers when imported
Run directly, the file sees '__main__' and calls main(); imported, it sees 'helpers' and skips it.

So the guard lets one file act as a script and as an importable module. If running files from the terminal is new to you, my guide on how to run a Python script covers the setup.

The import half of that test is the same thing that happens when you import a Python file from the same directory: top-level code runs, but the guarded block doesn’t.

Read command-line arguments in main() with sys.argv

sys.argv is a list of strings. Item 0 is the script name, and everything after it is what you typed after the name, split on spaces. Quotes keep a value with a space together:

import sys

def main():
    print(f"Script name: {sys.argv[0]}")
    print(f"Arguments:   {sys.argv[1:]}")
    print(f"Count:       {len(sys.argv) - 1}")

if __name__ == "__main__":
    main()

Output:

C:\pyguides> python show_args.py
Script name: show_args.py
Arguments:   []
Count:       0
C:\pyguides> python show_args.py sales.csv 2026-10-20 --verbose
Script name: show_args.py
Arguments:   ['sales.csv', '2026-10-20', '--verbose']
Count:       3
C:\pyguides> python show_args.py "New York" 25
Script name: show_args.py
Arguments:   ['New York', '25']
Count:       2
Command Prompt running a Python main function three times with different command-line arguments read from sys.argv
With no arguments the list is empty; quoting "New York" keeps it as one argument, and 25 arrives as a string.

Notice that '25' and '2026-10-20' are strings. sys.argv never converts anything, and a flag like --verbose is just one more string in the list.

For a side-by-side look at sys.argv, argparse and flag handling as a general CLI topic, see Python command-line arguments. Here, I’ll stay focused on how main() receives them.

Pass sys.argv[1:] into main() as a parameter

Reading sys.argv deep inside your code makes it hard to test. A cleaner habit is to give main() a parameter and pass the arguments in from the guard. This order-total script also converts each price and returns an exit code:

import sys

def main(args):
    if not args:
        print("Usage: python order_total.py PRICE [PRICE ...]")
        return 1
    prices = [float(a) for a in args]
    print(f"Items: {len(prices)}")
    print(f"Total: ${sum(prices):,.2f}")
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Output:

C:\pyguides> python order_total.py 19.99 5.25 3
Items: 3
Total: $28.24
C:\pyguides> python order_total.py
Usage: python order_total.py PRICE [PRICE ...]
C:\pyguides> echo %ERRORLEVEL%
1
C:\pyguides> python order_total.py 19.99 ten
Traceback (most recent call last):
  File "C:\pyguides\order_total.py", line 13, in <module>
    sys.exit(main(sys.argv[1:]))
             ^^^^^^^^^^^^^^^^^^
  File "C:\pyguides\order_total.py", line 7, in main
    prices = [float(a) for a in args]
              ^^^^^^^^
ValueError: could not convert string to float: 'ten'

Three prices give a $28.24 total. With no arguments, main() prints a usage line and returns 1, which echo %ERRORLEVEL% confirms. That’s the exit code Windows saw.

The last run shows the weak spot: float('ten') raises a ValueError traceback. You’d have to validate every value yourself, as in checking if a string is an integer or float, or let argparse do it.

Converting text to numbers is covered in converting a string to a float in Python, and the integer version works the same way with int().

Use argparse in the Python main function for named options

argparse ships with Python. You describe the arguments once, and it converts types, fills in defaults, writes the help text and rejects bad input. Here’s a bill-splitting script with one required value and three options:

import argparse
import sys

def build_parser():
    parser = argparse.ArgumentParser(description="Split a restaurant bill, tip included.")
    parser.add_argument("bill", type=float, help="bill total in USD, before tip")
    parser.add_argument("-t", "--tip", type=int, default=18, help="tip percent (default: 18)")
    parser.add_argument("-p", "--people", type=int, default=1, help="number of people (default: 1)")
    parser.add_argument("-v", "--verbose", action="store_true", help="also show the tip amount")
    return parser

def main(argv=None):
    args = build_parser().parse_args(argv)
    tip = args.bill * args.tip / 100
    share = (args.bill + tip) / args.people
    if args.verbose:
        print(f"Tip ({args.tip}%): ${tip:.2f}")
    print(f"Each of {args.people} pays: ${share:.2f}")
    return 0

if __name__ == "__main__":
    sys.exit(main())

Output:

C:\pyguides> python tip_split.py 84.50
Each of 1 pays: $99.71
C:\pyguides> python tip_split.py 84.50 --tip 20 --people 3 -v
Tip (20%): $16.90
Each of 3 pays: $33.80
Command Prompt running a Python main function that uses argparse, first with defaults and then with tip, people and verbose options
The first run uses the default 18% tip for one person; the second sets a 20% tip, three people and -v to print the tip line.

A few things are going on. bill is a positional argument, so it’s required. --tip and --people are options with short forms -t and -p, and type=int converts them for you.

action="store_true" makes -v an on/off flag: it’s False unless you type it. The parsed values come back as attributes, like args.bill and args.people.

Defaults on the command line work like default parameters on a function, which I cover in Python functions with optional arguments.

The free –help page and error messages

You didn’t write a help screen, but argparse built one from the help= strings. It also refuses a bill that isn’t a number, before your code runs:

import argparse
import sys

def build_parser():
    parser = argparse.ArgumentParser(description="Split a restaurant bill, tip included.")
    parser.add_argument("bill", type=float, help="bill total in USD, before tip")
    parser.add_argument("-t", "--tip", type=int, default=18, help="tip percent (default: 18)")
    parser.add_argument("-p", "--people", type=int, default=1, help="number of people (default: 1)")
    parser.add_argument("-v", "--verbose", action="store_true", help="also show the tip amount")
    return parser

def main(argv=None):
    args = build_parser().parse_args(argv)
    tip = args.bill * args.tip / 100
    share = (args.bill + tip) / args.people
    if args.verbose:
        print(f"Tip ({args.tip}%): ${tip:.2f}")
    print(f"Each of {args.people} pays: ${share:.2f}")
    return 0

if __name__ == "__main__":
    sys.exit(main())

Output:

C:\pyguides> python tip_split.py --help
usage: tip_split.py [-h] [-t TIP] [-p PEOPLE] [-v] bill

Split a restaurant bill, tip included.

positional arguments:
  bill                  bill total in USD, before tip

options:
  -h, --help            show this help message and exit
  -t TIP, --tip TIP     tip percent (default: 18)
  -p PEOPLE, --people PEOPLE
                        number of people (default: 1)
  -v, --verbose         also show the tip amount
C:\pyguides> python tip_split.py lunch
usage: tip_split.py [-h] [-t TIP] [-p PEOPLE] [-v] bill
tip_split.py: error: argument bill: invalid float value: 'lunch'
C:\pyguides> echo %ERRORLEVEL%
2
Command Prompt showing argparse --help output for a Python script and the invalid float value error with exit code 2
--help lists every argument with its help text; the word lunch is rejected as an invalid float and the exit code is 2.

argparse exits with code 2 for usage errors and 0 after --help. Both happen inside parse_args() by raising SystemExit, so your main() code after that line never runs.

The main(argv=None) pattern: a Python main function you can test

Look again at def main(argv=None) in the tip script. parse_args(None) reads sys.argv[1:], so the command line works as usual. But you can also pass a list of strings, exactly as if they’d been typed:

from tip_split import build_parser, main

args = build_parser().parse_args(["120", "--people", "4"])
print("Parsed:", vars(args))
print("Exit code:", main(["120", "--people", "4"]))
assert main(["60", "-t", "15"]) == 0
print("Tests passed")

Output:

Parsed: {'bill': 120.0, 'tip': 18, 'people': 4, 'verbose': False}
Each of 4 pays: $35.40
Exit code: 0
Each of 1 pays: $69.00
Tests passed

Importing tip_split didn’t start the program, thanks to the guard. Then I called main() twice with my own argument lists, and the assert checked its return value. No subprocess, no terminal needed.

That’s why I write almost every script this way. Tests stay plain function calls, and an assert or a pytest test can check the result directly.

Keeping build_parser() in its own function helps too: a test can par