Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

A Custom Parser

00:00 An even more flexible option than a regular expression is to write a custom parser that uses more advanced Python logic to parse input in pretty much any format you can imagine.

00:13 Let’s take a look at that in Python code with another implementation of the seq utility. Here, I have a new version of the seq utility which is going to be implemented with a custom parser.

00:26 Let me just add that in up here just so that it’s clear to everyone what’s going on here. Okay. So that’s there. And as you can see, the usage here is exactly the same as it was for the regex implementation.

00:40 It takes in the Python file, and then a possible --help option, a separator, and then optional first and incr (increment), but it must include a last operand.

00:50 The seq() function is actually exactly the same as well. It takes in this list of operands, which is a list of integers and a separator, which by default is newline ("\n").

01:02 And then based on whether there’s one, two, or three operands, it constructs the actual logic of the seq program. The parse() is what I’m going to end up writing here, and it’s going to be much more complex than it was in the regex expression.

01:16 But where complexity is gained in parse(), it’s lost in the fact that there’s no actual regex to deal with. But the main() also works quite the same way, where it actually gets the separator and the operands from the parsing process, and then simply says if there are no operands, raise SystemExit with the USAGE, otherwise, use seq() to actually construct the string that needs to be returned.

01:40 So, how does this actual parsing logic work? How is it possible to just parse through this without using something like a regex? Well, the idea is that you want to parse from left to right, one argument at a time, and follow certain rules based on what should happen with each argument.

01:57 So, that kind of structure suggests that it might be nice to use what’s called a double-ended queue, which is essentially a queue, or a list, that lets you pop really quickly and easily from both sides—from the left and the right.

02:11 We want to pop from the left, generally. So, the arguments is going to be a double-ended queue of the arguments, and then I’ll just say for now that the sep is equal to newline, even though that would actually be taken care of by the default argument to seq(), but I just want to have this here for clarity’s sake.

02:27 And then, at the moment, there are no operands so far, so operands is just an empty list even though, eventually, it will return a list of the operands. Now, the next thing I want to say is while argumentsso, while arguments has any members at all—I want to get the current argument, which is equal to arguments.popleft().

02:47 So, I’m popping—or getting access to—the left-most member of arguments, and then I’m taking it away from the arguments list as I go along.

02:56 Now, the first case is if there are no operands whatsoever so far, so if len(operands) == 0. That’s the only time when you want to be checking for the --help or the sep options. So, if current == "--help",

03:15 then what you want to do is you want to print out the USAGE, and then I actually just want to exit from the system, but I want to exit() with the status code 0 to make sure that anyone looking at this knows that this wasn’t a failure—this was a designed exit.

03:31 The next thing that could happen is current could be in either the short form of

03:38 --separator or the long form, here. Right? So, if it’s either one of those things, what I want to do is I want to say sep = current, which should work just fine because current is an argument, so it’s a string.

03:52 Then, I want to continue because I don’t want to do any more logic after this, I just want to get the separator. And I said sep = current but of course that doesn’t make sense because then it would just make sep to be "-s" or "--separator".

04:03 So what I actually want is I want to say sep = arguments.popleft(), so actually get the next argument from current instead of the actual current operator, which is one of these two.

04:16 So, the next thing I want to say is, well, “What happens if I’m not looking to parse "--help" or "--separator"?” Right? The next thing that I want to do is I want to actually try to get access to an operand, right?

04:29 Because anything other than "--help" or sep is just an integer operand. So, the first thing I can say here is try—and I’ll tell you why I’m using a try and except block in a second—but I’ll say try: operands.append() the integer form of current.

04:46 And it may just have become clear why this try and except block was necessary, because it could be the case that current is not actually a parseable integer.

04:54 So in that case, I’ll except a ValueError and I’ll raise SystemExit with the USAGE message, so

05:01 that they know that they’ve made some mistake here. And then the final last thing I want to do is I want to say if operands—or, I should actually be more precise—if len() of operands is more than 3so, if this is the fourth or greater argument—then I also want to raise SystemExit, because someone has passed in a malformed number of operands there. And then with all of that done, I can simply return in the correct order the separator, and then the operands.

05:32 So, that’s how you can use this parsing logic along with a double-ended queue to go through and follow simple rules, and by following those simple rules, you get some great properties. So for example, --help and --separator have to come before any operands in this case, right? Otherwise, if you see them in any other place you’ll get this ValueError because they aren’t integers, right?

05:54 So if there are any operands, then there needs to between one and three, and there needs to be at least one, and otherwise, there’ll be some kind of error that’s raised just simply based on the behavior of this while loop structure.

06:07 So, this is really convenient and really cool. Now let’s watch it work in the terminal. So, I can say here python seq_parse.py and let’s first try it with this --help flag.

06:18 And there you get the usage, so it works just fine. And then a simple test case, here, just passing in 10—works great. And then my classic example—going up by 2, and then going up by 1—works perfectly fine.

06:32 And let’s try with the --help flag after some operands. Well, that raises a usage, just as it would if I passed in some kind of malformed argument, like this --s.

06:43 Now, let’s use the separator real quick, and see if this works. So, I’ll say here—maybe my separator would be the three letters "AAA". I don’t know why you would use that, but there—you actually get that just as desired, so this works just fine.

06:57 Writing a custom parser can be a really flexible and really powerful approach, but the problem, of course, is that this logic here that I showed you in parse()it also requires a lot of maintenance just like a complex regular expression.

07:12 So really, you haven’t lost any complexity by moving to this custom parsing version, rather than regex. You’ve just moved that complexity to your code logic rather than your regex logic.

07:24 So really, both of these approaches—as awesome as they are—are something that really needs to be automated, and so that’s what I’ll talk about in the next section of this series when I’ll talk about the tools that exist in Python to automate this stuff for you. But as of now, you have a great understanding of how all of this works under the hood, and you should be really well prepared to understand any command line parsing system that you encounter, at least on a basic level. In the next lesson, I’ll talk some about type validation.

Become a Member to join the conversation.