Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
250: The argparse Module Revisited
I remember a project a few years ago where I wrote a script to migrate a massive database. I thought I was being clever by adding a few basic flags for the source and destination. I handed it off to my lead DevOps engineer, and within ten minutes, he came back to my desk. He had tried to pass a list of table names to a flag I'd designed for a single string, and the script had just choked. Then, he tried to use a --dry-run flag and a --force-execute flag at the same time. The script didn't know how to handle the contradiction, so it just did both—which is a terrifying prospect when you're dealing with production data.
That's when I realized that while basic argparse is easy, building a robust CLI tool requires a bit more intentionality. You aren't just capturing strings; you're designing an interface for another human (or a shell script) to interact with your logic. If you've already used argparse for simple flags, it's time to look at the features that prevent the kind of chaos I experienced that Friday afternoon.
Handling Lists and Custom Type Validation
Most of us start by using type=int or type=str. But argparse is actually much more flexible. The type argument can be any callable that takes a string and returns a converted value. I often use this to validate inputs before the main logic of my program even starts. For example, if you need a number between 1 and 100, don't wait until you're inside your main() function to check that; do it in the parser.
Then there's the nargs parameter. If you want to allow a user to pass multiple values—like those table names my lead engineer wanted—you can use nargs='+' (one or more) or nargs='*' (zero or more). This automatically turns that argument into a list in your resulting namespace.
import argparse
def percentage(value):
ival = int(value)
if ival < 0 or ival > 100:
raise argparse.ArgumentTypeError(f"{value} is not a percentage")
return ival
parser = argparse.ArgumentParser()
parser.add_argument('--threshold', type=percentage, help="Threshold from 0-100")
parser.add_argument('--files', nargs='+', help="One or more files to process")
args = parser.parse_args()
# If the user runs: python script.py --threshold 110
# argparse handles the error and shows a helpful message automatically.
Preventing Conflicting Flags with Mutually Exclusive Groups
As I mentioned with the --dry-run and --force-execute disaster, some flags simply shouldn't exist together. You don't want to write a bunch of if args.dry_run and args.force: checks throughout your code. Instead, you should use a mutually exclusive group. This tells argparse to throw an error if the user tries to provide both.
I find this is the best way to keep my CLI tools from entering an "undefined state." It forces the user to be explicit about what they want the script to do.
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('--verbose', action='store_true')
group.add_argument('--quiet', action='store_true')
# Now, running 'python script.py --verbose --quiet' will result in:
# error: argument --quiet: not allowed with argument --verbose
Organizing Complex Tools with Sub-parsers
Eventually, your script might grow from a "single-purpose utility" to a "toolset." Think of how git works: you don't run git --commit; you run git commit. This is achieved using sub-parsers. This allows you to have entirely different sets of arguments depending on the command being called.
This is where argparse really starts to feel like a professional framework. You can define a main parser and then add "sub-commands" to it. Each sub-command can have its own help text and its own required arguments, keeping your global namespace clean.
parser = argparse.ArgumentParser(prog="tool")
subparsers = parser.add_subparsers(dest="command")
# 'upload' command
upload_parser = subparsers.add_parser("upload")
upload_parser.add_argument("filename")
# 'download' command
download_parser = subparsers.add_parser("download")
download_parser.add_argument("--id", required=True)
args = parser.parse_args()
if args.command == "upload":
print(f"Uploading {args.filename}")
elif args.command == "download":
print(f"Downloading ID {args.id}")
📋 Practical Task
Exercise: Building a Multi-Command Log Processor
You need to build a CLI tool called logtool.py that helps a system administrator analyze server logs. The tool must support two distinct sub-commands: filter and stats.
- The
filtercommand:- Must take one or more
filesas positional arguments (usenargs). - Must have an optional
--levelflag that only accepts "INFO", "WARNING", or "ERROR". (Hint: use thechoicesparameter inadd_argument).
- Must take one or more
- The
statscommand:- Must have two mutually exclusive flags:
--summaryand--detailed. - Must have a
--thresholdflag that takes an integer. Use a custom type function to ensure this integer is positive (greater than 0).
- Must have two mutually exclusive flags:
Your script should not actually process any logs; it should simply parse the arguments and print a confirmation message showing what the user requested (e.g., "Filtering files [a.log, b.log] for level ERROR" or "Calculating detailed stats with threshold 10").
There are no comments for now.