Loading video player…

Basic Error Handling

00:00 In this lesson, I’ll show you how to do some basic error handling as you use the sys.argv list to process command line arguments.

00:10 Let’s take a look at some actual Python scripts that make use of sys.argv, and I’m going to try to point out possible problems that might arise and ways to deal with them—though, of course, I can’t promise that I’ll identify every possible issue you’ll ever run into with sys.argv. I’m still running into problems when I use it in my own code, and I’ve been working with this for ages.

00:31 So, taking a look at this argv.py script, this is very simple. All it does is take in arguments and then print them back out.

00:41 And it just prints them back out using this new debugging specifier, which is new in Python 3.8. If you don’t have 3.8, you can just delete this equal sign and it will actually run exactly the same way. It just won’t print out the name of the variable as well as its value.

00:58 It’ll just print out the value alone. But all of that is notwithstanding the actual thing that it’s doing here, which is just pointing out that the zeroth index of sys.argv always contains the filename that’s running, whereas the rest always contains the arguments. So of course, if you run this with no arguments, the rest—the actual args—will just be an empty list.

01:20 I’ll show you as I move over into reverse.py how that can be a problem.

01:26 This reverse.py script just takes in one argument, accessed via sys.argv at the

01:33 first index, and then it prints out the reversed version of that argument using Python’s list slicing operator. So I’ll show you how it works. I’ll say python reverse.py and then I’ll put in my name, Liam.

01:47 And as you can see, it outputs maiL, which is my name in reverse. However, being the conscientious Python programmers that you are, you have certainly realized that this doesn’t do any error handling at all.

01:59 And of course, whenever you assume there’s at least one element in a list, that’s a recipe for disaster. So, if I call this with no arguments whatsoever, as you might guess I get an IndexError: list index out of range because there are zero elements in that list.

02:14 So, one way to handle this is by using a try and except block. I can simply say try this, and then I can say except IndexError.

02:24 And it’s always good practice to write the specific kind of error that you’re excepting, rather than just a more general error. Then, I can print out "Expected 1 argument, got 0" and raise SystemExit.

02:42 And this will work just fine, on this case. It will at least give your user a little bit more verbose in output so that they can know what’s wrong here. Otherwise, this IndexError doesn’t say why something went wrong.

02:53 It just says that something did in fact go wrong.

02:57 Now, there’s another problem with this too, which you might have noticed, which is that there’s a little bit of an unexpected behavior if I enter in more than one argument.

03:04 So, say I put in Real Python. All I actually get out is the reverse of Real, and that’s because these two words are being interpreted as two separate arguments. Now, if that’s the behavior that you want—if you want to only ever reverse the first argument without dealing with any of the others—then this might just be fine.

03:25 But you might also want to put in some conditional logic in here to check whether there’s more than one argument and just say, huh, maybe the user thinks that they’re entering, for example, a string, like "Real Python" in quotes, but in fact, they’re actually entering multiple arguments.

03:42 So, that might be something to watch for, and it’s not necessarily an error, but it can be a problem in the logic of your program. Now, one more thing to notice is just that I wrapped in quotes this "Real Python" in order to reverse the whole thing. I can also wrap in single quotes…

03:59 and I can even not use any quotes at all and I can use the backslash to escape this space character, which means, essentially, don’t treat it as a whitespace character—treat it as just part of this string.

04:12 So, those are the things you can do to make multiple arguments into one, essentially, or into one string argument. Otherwise, any whitespace will delimit a new argument just based on how Bash shells work, and that’s the same in Windows with PowerShell and things like that.

04:27 So, that’s something really important to note. Next up, I’ll show you how the global nature of sys.argv can cause some issues.

04:36 argv_pop.py just uses the sys module to print out the arguments to the program before and after popping

04:45 from the sys.argv list. And of course, .pop() just gets and removes the last element of a list, so this is a case of directly modifying sys.argv.

04:57 Now, this can be troublesome because when I run this with some arguments—so I can just say Some Stuff, here—as you’ll notice, before I pop from this list, sys.argv has one set of contents, and after, it has a different one.

05:12 This can definitely be a problem when you have a large codebase and a lot of programmers working on one project, because sys.argv is always global to your Python invocation. So, if multiple different programmers want to access it, then they might start to run into issues whereby if one programmer modifies it, the next doesn’t actually get the real arguments, but some kind of truncated or modified version of it.

05:36 So, best practice is to say something like args = sys.argv at 1, on—

05:43 and you could also do the whole, you know, you could just copy sys.argv if you also needed the filename, but often you really just need the arguments. And then, when you modify things, you should actually just modify the version that you’ve copied over.

05:56 So of course, when I run this, sys.argv always remains constant. This is a very powerful programming practice because it lets anyone anywhere in your code have access to the original system arguments, but you can also just pass around whatever version of this you want to pass around, as long as everyone in your codebase is on the same page.

06:16 So, that’s an important thing to remember: that sys.argv is global and any modifications to it will be propagated throughout your Python invocation.

06:23 Finally, I want to show you how to work with files as command line input.

06:29 This program cat.py uses the sys module to get an access to an argument and then open that argument in read mode as a file and then prints the contents of the file.

06:41 This is an attempt to mimic the Linux utility cat in a very basic form. The Linux utility cat just prints out the contents of some number of files. To demonstrate this, I’ll use some other Linux command line utilities to make a file—so, I’ll say touch test.txtand then I’ll use echo to put some actual text into test.txt.

07:03 So, I’ll say "Here is some test text", and then I can put that into test.txt using terminal operators. And then finally, I’ll use the Linux utility cat to cat test.txt and show the

07:19 contents of the file. Now, let me show you the Python version of this, where I say python cat.py, and then I can just put in test.txt.

07:28 As you can see, it does almost the same thing. It adds a newline because that’s how Python’s print() works, but other than that it’s exactly the same behavior here. Of course, cat is much more powerful.

07:39 It can do any number of files and it can give you some interesting information about them too, but this is just a basic version. But of course, the problem here is that if I run this on some file that doesn’t exist, right—doesnotexist.txt—then I’ll get a FileNotFoundError. And again, this is an issue because your user, if they try to use this, will know that there was a problem and they’ll know where it occurred, but they might not know why there was a problem, right?

08:06 And so what you can do here to fix this is, as always, you can wrap this in a try and except block.

08:15 So, you can say try this stuff—that’s there—and then except FileNotFoundError. And then you could print a useful message. I’ll use an f-string to make it a little more informative.

08:29 f"The file {arg} does not exist, try a different file". Something like that. And then, as always, raise SystemExit, here.

08:41 And now, you can see if I do the same call again, then it tells me a little bit more helpfully, "The file doesnotexist.txt does not exist, try a different file". Of course, in this case, you should be able to guess from the filename that it doesn’t exist, but in other cases, that won’t be so easy. Now, as an exercise to you, I want you to fix this part of the code, which is the same as what we used in reverse.py, and so you can use similar strategies to enforce the fact that this takes just one argument at this moment. So, if I call this with no arguments, again, I’ll get an error.

09:16 Another interesting thing that you could do would be to try to mimic the behavior of cat a little bit more closely and print out any number of arguments, or any number of file contents passed in as arguments.

09:30 You now know how to handle a lot of the common errors that you might find when you work with sys.argv in Python. In the next lesson, I’m going to pivot a little bit and show you a little bit more about how Python works as an actual command line interface itself, because there’s a lot more to it than just saying python and then a Python file.

Avatar image for andresfmesad

andresfmesad on Nov. 5, 2021

Here is my solution:

import sys

# Capture the list of files
try:
    list_files = sys.argv[1:]
except IndexError:
    print("One or more files expected")
    raise (SystemExit)


# try to cat in every file
for file in list_files:
    try:
        with open(file, 'r') as infile:
            print(infile.read())
    except FileNotFoundError:
        print(f"\nThe file {file} does not exist")
        raise (SystemExit)

Become a Member to join the conversation.