[MUSIC PLAYING] DAVID MALAN: All right. This is CS50, and this is the day before our test, of course. But this is lecture 8, in which we're actually going to finally transition from C, this lower-level language that we've been spending quite some time to. And the goal today isn't so much to focus on Python per se, but honestly to do what we hope will be one of the most empowering aspects of the class, which is to emphasize that this has not been a semester learning C. This has been a semester learning programming, a certain type of programming called procedural or imperative programming. But more on that in another higher-level class perhaps. But really, that this class is about ultimately teaching yourself to learn new languages. And indeed, what you'll find is that as we explore some of the features and the syntax of Python, odds are today, it might look as cryptic as C did just a few weeks ago. But you'll find that once you start recognizing patterns, as you have with C, it will be all the more accessible and all the more useful when solving some problems. So unrelatedly, just earlier this week, I happened to be in Mountain View with some of the team. And you might recall from last lecture at Harvard we offered this glimpse of one of the earliest racks of servers that Google itself had. Well, turns out they changed buildings. But we happened to stumble upon the actual display. So pictured here is a photo from my own phone, which was actually really cool to see. So inside of this, you'll see all of the old hard drives they've used. We actually looked at some of the labels. And indeed, hard drives manufactured in 1999, which is when Google started getting some of its momentum. You can see the green circuit boards here, on which would be CPUs and other things, potentially. So if you'd like a stroll down memory lane, feel free to read up on this on Wikipedia or even on the excerpts here. And then strangely enough, at the conference some of us were at did we discover this-- perhaps the biggest duck debugger made up of smaller duck debuggers, one of whom was our own. So that, too, was how we spent this past week. All right. So how are we going to spend this week and the weeks to come? So you'll recall that when we transitioned from Scratch to C, we drew a couple of comparisons between syntax and features. And I thought it'd be useful to take that same approach here, really to emphasize that most of the ideas we're going to explore today are themselves not new. It's just how you express them and how you write the syntax in the language known as Python that's indeed going to be different from Scratch, from C, and now here we are with Python. So back in the day, in week 0, when you wanted to say something in Scratch, you would literally use this blue purple puzzle piece, say hello. And we called that a function or a statement. It was some kind of verb action. And in C, of course, it looked a little something like this. Henceforth, starting today in Python, it's going to look like this. So before, after. Before, after. So it's pretty easy to visually diff these two things. But what are just a couple of the differences that jump out at you immediately? C, Python. So there's no more backslash n, it would seem, in this context. So that's kind of a nice relief to not have to type anymore. What else seems to be different? No semicolon, thank god. Right? Perhaps the stupidest source of frustration that you might have experienced by just omitting one of those. And someone over here? Yeah, so printf is now just print, which is pretty reasonable unto itself. So these are terribly minor differences. But it's sort of testament to the kinds of mental adjustments you're going to have to start to make. Fortunately, thus far we've seen that you can start leaving things off, which is actually a guiding principle of Python in that one of its goals is it's meant to be easier to write than some of its predecessors, among them C. So in C we might have implemented this hello, world program that actually ran when you clicked the green flag using code like that at the right. And this was, if those of you who had no programming experience coming in to CS50, what probably looked like the proverbial Greek to you just a few weeks ago. And we teased apart what those various lines meant. But in Python, guess what? If you want to write a program whose purpose in life is to say, hello, well, just write def main. Print hello, world. So it's a little similarly structured. And in fact, it does not lack for some of the more arcane syntax here. But we'll see soon what this actually means. But it's a little simpler than the one before. And let's tease this apart. So def here simply means define me, a function. So whereas in C we've historically seen that you specify the type that the function should return, we're not going to do that in Python anymore. Python still has data types, but we're not going to explicitly mention what data types we're using. Meanwhile, here is the name of the function. And main would be a convention, but it's not built into the language in the same way as it is in C, as we shall see. Meanwhile, this silly incantation is just a way of ensuring that the default function to be executed in a Python program is indeed going to be called main. But more on that when we actually start creating. But this is perhaps the most subtle but most important difference, at least early on. And it's even hard to see at this scale. But notice the colons both here and here that I've highlighted now in yellow, and these dots, which are not to be typed, but are just meant to draw your attention to the fact that I hit the space bar four times in those locations. So if you have ever sort of gotten some feedback from your TA or TF that your style could be better, closer to 5 out of 5, because of lack of indentation or pretty formatting, Python's actually gonna help us out with this. So Python code will not run if you have not invented things properly. So gone are the curly braces that encapsulate related lines of code within some block of functionality. And instead, they're replaced generally with this general structure. You have a colon, and then below that and indented are all of the lines that are somehow related to that earlier line of code. And the indentation must be consistent. So even though your own eye might not quite distinguish four spaces from three, the Python environment will. And so this will actually help implicitly enforce better style, perhaps, than might have been easily done from the get-go. So then, of course, in Scratch, we had a forever block, which says, hello, world forever, much like in C, we could implement it like this. Now there's actually a pretty clean mapping in Python. We already know we can get rid of the semicolon. We already know we can get rid of the curly braces. We're going to have to add in a colon. But it turns out we can get rid of a little more, too. So what more is absent from this translation of hello, world to Python? This one's more subtle. So we definitely got rid of the curly braces, relying now just on indentation. OK, so there's no parentheses around while. And so this, too, is actually meant to be a feature of Python. If you don't logically need parentheses to enforce order of operations, like in arithmetic or the like, then don't use them because they're just a distraction. They're just more to type. And the code now is just visually cleaner and easier to read. There's a minor difference, too-- True and False are going to be capitalized in Python. But that's a fairly incidental detail. But notice this kind of captures already the spirit of Python. It's not a huge leap to go from one to the other. But we've just kind of started to get rid of some of the clutter and the stuff that never really intellectually added much, and if anything was annoying to have to remember early on. So True here is our Boolean. And now we have a finite number of iterations. We might want to say hello, world exactly 50 times. In C, this was a crazy mess if you wanted to do this. You'd have to initialize a variable with which to count up to, but not including 50, plus plussing along the way and so forth. In Python, it's going to be a little cleaner. And we'll come back to what this means exactly. But if you kind of read it from left to right, it kind of says what you mean. Right? For i in the range of 50. So i is probably going to be a variable. And notice we're not mentioning its type. It's going to be implied by whatever the context is, which in this case has to do, apparently, with numbers, per the 50. Range is actually going to be a data type unto itself. It's a little funky in that sense. It's called a class. But this essentially is a special feature of Python that, unlike in C, where if you want to iterate over an array of values or 50 such values, you would literally have an array of 50 values. Range is kind of cool in that it kind of stands there. And every time you iterate through a loop, it hands you the next number, but just one at a time, thereby using maybe as little as one 50th the amount of memory because it only has to keep one number around at a time. And there's a bit more overhead than that. It's not a perfect savings, quite so. But this just says for i in range 50, and that's going to implicitly count from 0 up through 49. And meanwhile, what's below it is what's going to get printed this time. So meanwhile, here was one of our bigger Scratch blocks early on. And i translates pretty literally to code in C. And you can perhaps guess, if you've never seen Python before today, what the Python code might now look like. If this here on the right is the C code, what are some of the features syntactically that we're about to throw away? Yeah. AUDIENCE: You can throw away the curly braces and the parentheses. DAVID MALAN: Curly braces and parentheses are going to go away. What else might go away? The semicolons are going to go away. The backslash n inside of the print statements. Great. One more thing, I think. The if. So we don't strictly need the parentheses because it's not like I'm combining things logically like this or that or this and that. So it should suffice to get rid of those two. And there's a couple of other tweaks we're going to have to make here. But indeed, the code's going to be a lot tighter, so to speak. Now you're just going to say what you mean here. And there is one weird thing. And it's not a typo. What apparently are we going to have to start knowing now? Elif whatever. So elif is not a typo. It's indeed how you express the notion of else-if. But otherwise, everything is exactly the same. And notice the colons. Frankly, ironically, whereas previously it might have been annoying to occasionally forget a semicolon, now the colons my take on that role. But at least everything below them is meant to be indented. So here's a fundamental difference beyond the sort of silly syntactic differences of this and, say, other languages-- the flow of work that we've been using thus far has been essentially this in C. You write source code in a file generally ending in .c. You run a compiler, which, as a quick check, is called clang. So it's not technically make. Make is just this helpful build utility that automates the process of calling clang. So clang is, strictly speaking, the compiler. And clang outputs zeros and ones, otherwise known as machine code. And your computer-- Mac, PC, whatever-- has a CPU, Central Processing Unit inside, made by Intel or some other company. And that's CPU is hardwired to understand certain patterns of bits, zeros and ones, otherwise known as machine code. So that's been our world in C. With Python-- so the code that you might have compiled in C, for instance, might have been this, which we said we run clang on like this. And if you don't specify a default file name as output, you'll instead just get in your file all of the zeros and ones, which can then be executed by way of ./a.out, the default name for the assembler's output here. So in Python, though, the world gets here, too, a little simpler, as well. So we just now have source code and an interpreter. So there's no machine code, it would seem. There's no compiler, it would seem. And frankly, there's one fewer arrow, which suggests to me that the process of running Python code itself is actually going to be a little easier. Running C code has typically been two steps. You rerun clang, or via make you run clang. Then you run the program. And it's fine. It's not all that hard. But it's two steps. Why not reduce to two steps what you could instead do in one? And we'll see exactly what this means. Now, technically, that's a bit of an oversimplification. Technically, underneath the hood, if you wanted to run a program like this that simply prints out hello, world, you would simply run python hello.py. And the result of that would be to see hello, world on the screen, as we'll soon see. But technically, underneath the hood, there is some other stuff going on. So there actually kind of is a compiler. But there's not something called machine code, per se. It's called bytecode. There's even something called a Python virtual machine. But all of this is abstracted away for us, certainly for the sake of today's conversation, but also in the real world, more generally. Humans have gotten better over the decades at writing software and writing tools via which we can write software. And so a lot of the more manual processes and a lot of the lower-level details that we've been focusing on, if not struggling on, in C, start to go away. Because much like in week 0, where we started layering on idea after idea-- zeros and ones, ASCII, colors, and whatnot-- similarly with our actual tools are we're going to start to do the same. So whereas in actuality what's going on underneath the hood is this process here, we can start to think about it, really, as something quite simpler. Now, if you're curious, and if you take some higher-level class like CS61 or another, you'll actually talk about things like bytecode and assembly code and the like. And we saw a glimpse of the latter a bit ago. This happens to be an intermediate language that Python source code is converted into before it's run by the computer. But again, we're going to turn a blind eye to those lower-level details. So here are some of the tools now in our toolkit. In Python, there are data types, though as of now we've not seen any examples whereby I specify what types of values are going to be in my variables or what types of values a function's going to return. But they are there. Everything is sort of loosely typed in that whatever you want a variable to be, it will just take on that data type, whether it's an int or string or the like. It's not going to be the full word string. In Python, it's literally called str. But there are some familiar types here-- bool and float and int and others. And, in fact, among the others, as we'll soon see, are features like range. But before that, note too that we'll provide for at least our first foray into Python a few familiar functions. So Python has different mechanisms than C for getting input from the user. We've abstracted some of those details away in a new CS50 library for Python that you'll really just use one or few times before we transition away from even that, but will give you functions like get_char, get_float, get_int, get_string that handle all the requisite error checking so that at least for your first few programs, you can just start to get some real work done without diving into underneath the hood there. And then lastly, here are some other tools in our toolkit. And we'll just scratch the surface of some of these today. But what's nice about Python and what's nice about higher-level languages more generally-- like more modern languages that learned lessons from older languages like C-- is that you get so much more for free, so much more out of the box. There's so much more of a kitchen sink. There's so many metaphors we can use here, all of which speak to the fact that Python has more features than C, much like Java, if you took AP CS or something else, had than C. So does Python have a whole toolkit for representing complex numbers, for representing dictionaries, otherwise implemented as hash tables, as you now know; lists, which is kind of synonymous with an array. But a list is an array that can sort of automatically grow and shrink. We don't have to jump through hoops as we did in C. Range we've seen briefly, which just hands you back one number after another in some range, ideally for iteration. Set is the notion from mathematics, where if you want to put bunches of things into a data structure and you want to make sure you have only one of each such thing without duplicates, you can use a set. And a tuple is also a mathematical notion, typically where you can combine related things without complicating things with actual structs. Like, x, y is a common paradigm in lots of programs-- graphics, or videos, or certainly math and graphing itself. You don't really need a whole full-fledged data structure. You might just want to say, x, y. And so Python gives us that kind of expressiveness. So let's actually now dive in with that quick mapping from one world to the other and focus on what you can actually do with Python. So here I am in the familiar CS50 IDE. Much like we have pre-installed for you clang and make and other tools, we've also installed for you a program. That program is called Python, which is a little confusing at first glance because Python is apparently the name of the language. But it's also the name of the program. And here's where Python is different. Whereas C is again compiled, and you use something like clang to convert it to machine code, Python is both the name of the language and the name of the program you use to interpret the language. So pre-installed in CS50 IDE, and frankly, these days, probably on your own Macs or PCs, even if you don't know it, is a program called Python that if fed Python source code as input will do what that code says. So let's go ahead and try something just like that. Let me go ahead and save a file preemptively as hello.py. So .py will be the convention now instead of .c. And I'm going to go ahead and actually keep this pretty simple. I'm just going to print the first thing-- muscle memory. So it's not printf anymore. It's just hello, world. Save, done. That's going to be my first program in Python. Why? It's one line of code. It's consistent with the features I've claimed Python has. So how do I run it? Well, in C, we would have done, like, make hello. But make knows nothing about this because make is typically used with C, at least in this context here. So maybe it's, like, ./hello.py. No. It seems I don't have permission there. But there's a step that I teased us with earlier on just the slide alone. How do I go about running a program, did I say? AUDIENCE: Python hello.py. DAVID MALAN: Yeah. I have to be a little more explicit. So python, which is the name of the interpreter that understands Python. And now I need to feed it some input. And we know from our time in C that programs can take command-line arguments. And indeed, this program itself does, Python. You just give it the name of a program to run. And there it is, our very first program. So that's all fine and good. But what if I wanted to do something a little more interesting, like getting a string from the user? Well, turns out in Python, in CS50 IDE especially, I can do something like this. s gets get_string. And I can ask someone, for instance, for their name, like this. Now, CS50 IDE is already yelling at me-- undefined variable get_string. and let's actually see if maybe it's just buggy. No. So this is a little more arcane than usual. But traceback, most recent call last. File "hello.py," line 2, in module-- whatever that is. So I see a line of code from line 2. NameError-- name get_string is not defined. This is not the same language we've seen before, but what does this feel reminiscent of? Yeah, like in the past, when you've forgotten cs50.h, you've gotten something about an undeclared identifier, something like that. It just didn't understand something related to the CS50 library. So in C, we would have done include cs50.h. That's no longer germane because now we're in Python. But it's somewhat similar in spirit. Now I'm going to say instead from cs50 import get_string, and now save that. And hopefully momentarily, the errors will go away as the IDE realizes, oh, you've now imported the CS50 library, specifically a method or function, rather, inside of it called get_string. So there, too, it's different syntax, but it kind of says what it means-- from cs50, which is apparently the name of the library, import a function called get_string. Now if I go ahead and rerun python hello.py, I can go ahead and type in, say, Maria's name and ignore her altogether because I need to make a fix here. What's the obvious bug-- obvious now, to me-- in the program? AUDIENCE: You need to include the variable for s. DAVID MALAN: Yeah. So I need to include s, which I got on line 3, but didn't thereafter use in any way. So this is going to be wrong, of course, because that's going to say, literally, hello s. This is kind of how we used to do it. And then we would put in s. But this is not printf. This is print. So the world is a little different. And it turns out we can do this in a couple of different ways. Perhaps the easiest, if least obvious, would be something like this, where I could simply say hello, open curly brace, close curly brace. And then inside of there, simply specify the name of the variable that I want to plug in. And that's not quite all the way there. Let me go ahead and run this once more. Now if I type in Maria's name, oh. Still not quite right. I need to actually tell Python that this is a special type of string. It's a formatted string, similar in spirit to what printf expected. And the way you do this, even though it's a little different from C, is you just say f. This is an f string. So literally before the quotes, you write the letter f. And then if I now run this program here, i'm going to actually see Maria's name as hello, Maria. And I'll take care of that red X later. So that's a format string. And there's one other way. And this is not very obvious, I would say. You might also see in online documentation something like this. And let's just tease this apart for just a second. It turns out in Python that what I've highlighted in green here is known as a string, otherwise known as a str. str is the name of this data type. Well, unlike in C, where string was kind of a white lie, where it was just a pointer at the end of the day, a string is actually a first-class object in Python, which means it's not just a sequence of characters. It has built-in functionality, built-in features. So much like a struct in C had multiple things inside of it, so does a string in Python have multiple things inside of it, not just the sequence of characters, but functions that can actually do things. And it turns out you access those functions by way of the same dot operator as in C. And then you would only know from the documentation or examples in class what functions are inside of the string object. But one of them is format. And that's just a function that takes an argument-- what do you want to plug into the string to the left of the dot? And so simply by specifying, hey, Python, here's a string with a placeholder. Inside of this string is a built-in function-- otherwise known as a method, when a function is inside some object or structure-- pass in the value s. So if I now go ahead and rerun this after saving my changes, I should now see that Maria's name is still plugged in. So that's it. But a simple idea that now even strings have things inside of them besides the characters alone, and you can access that via the dots. So let's go ahead now and ramp things up to a more familiar example from a while back. Let me go ahead and open up two side-by-side windows and see if we can't translate one to the other. I'm going to go ahead and open up, for instance, int.c from some time ago. So you might recall from int.c, we had this program, whose purpose in life was to get an integer from the user and actually now plug it into printf, and then print it out. So what's going to be different now in Python? Well in Python, if I go ahead and implement this as, say, int.py, I'm going to go ahead and do the following. Let me scroll down to kind of line things up roughly. I can go ahead and say def main, as I saw in the slides before. And then over here, I can say i gets get_int, quote, unquote, integer. And then down here, I'm going to say not printf but print, quote, unquote, "hello," and then the placeholder. What's the simplest way to do this now, per our past example? Curly brace i. And then I just need to be super clear this is a special f string or format string, into which you can plug in values. And now I'm going to go ahead and save that. And I've got most of the pieces together, ignoring, for now, the red X. So what more remains to be done? I've made one same mistake as before. Yeah, so the get_int. so up here, really the equivalent of line 3 would be from cs50 import get_int this time. Saving that. And now if in my terminal window I go ahead and run python of int.py-- hmm. That seems strange. It's not an error, in terms of, like, erroneous output. Just nothing happened. So why might this be? How might you go about troubleshooting this, even with very little Python under your belt? Was that a hand, or no? No? OK. Yeah? AUDIENCE: Is there a line break? DAVID MALAN: Is there a line break? That's OK. I was just doing that to kind of make everything line up. But it's no big deal. Everything's indented properly, which is the important aesthetic. Yeah. AUDIENCE: We didn't call the function. DAVID MALAN: We didn't call the function. And this is where Python's a little different from C. In C, recall, main just gets called automatically for you. Humans years ago decided that shall be the default name of a function. In Python, line 6 here, calling something main is just a convention. I could have called it foo or bar or any other word. It has no special meaning. And so in Python, if you want to actually call main, you need to do something, frankly, that's, I think, one of the stupider distractions early on. But you have to literally say this-- if the name of this file happens to equal something that's specially called main, then call main. So long story short, when you run the Python interpreter on a file, as we've been doing with python, space, int.py or hello.py, there is a special global variable that your program has access to called __name__. And if that default name happens to be __main__, then you know that you have the ability to call any function you want by default. So for now, much like we did in week one, where we glossed over certain details that just weren't all that interesting, lines 11 and 12, for now, let's consider not all that interesting. But it's how we're going to kick-start these programs. Because now if I run python, space, int.py, type in a great number-- hello, 42. That's the meaning of life, the universe, and everything. So let's now actually do something more powerful than just getting a single int from the user. Let me go ahead and close off this one and close off this one and open up, say, ints.c after splitting my window again into two windows here. And let's open ints.c. So this one was a little different in that we did some arithmetic. And so here is going to be another difference in Python. Here's what we did in C. And what was curious or worth noting about math in C? Which of these did not quite behave as you might expect in the real world? Division? Yeah, why? What did division do? Yeah, it chopped off or rounded down. It floored the value by throwing away everything after the decimal point. So this line here, 18, where it's such-and-such divided by such-and-such is such-and-such. And we literally just said x divided by y. If you divided, for instance, 1 divided by 2 in grade school, hopefully, you would get the value 1/2 or 0.5. But in C, what did we get instead? AUDIENCE: Zero. DAVID MALAN: Zero. So it gets truncated to an int, the closest int without a decimal point being 0 because 0.5 is really 0.5. And thus we had that effect. So in Python, things are going to be similar in spirit. But this is kind of a feature that was fixed or a bug that was fixed. In Python-- let me go ahead here and open up an example I wrote in advance called ints.py, which is actually now going to look like this. So the Python equivalent now, which I've roughly line up, looks a little different. And there's a few distractions because we have all these f strings. Now in the way. But notice I'm just plugging in x's and y's. But what's a new feature, apparently, in Python, arithmetically? So floor division. So this was the more proper term for what C has been doing all this time. In C, when you use use the slash and you divide one number by another, it divides, and then floors it to the nearest int. In Python, if you want that same old-school feature, you're going to now use slash slash, not to be confused with the C comment. And if you want division to work the way you always knew it did in grade school, you continue using just the slash. So a minor point, but one of the differences to keep in mind. So if we actually run this here in Python, if I go into source 8 today and our week's directory for week 1, and I run Python ints.py, here now we're going to see 1 and 2. And there's all of the values that we would expect to see. All right. So without dwelling too much on this, let's fast forward to something more powerful like conditions. So in Python, if we want to do something only conditionally, laying out my browser like this, let me go ahead and open up, let's say, conditions.py. Sorry, conditions.c, which once upon a time looked like this. So in this example here, notice that we have a program that gets two ints from the user, and then just compares x and y and x and y and prints out whether they're greater than, less than, or equal to, ultimately. So let's actually do this one from scratch over here on the right. So let me go ahead and save this as conditions.py. And then at the top, what's the very first thing I'm going to apparently now need? Yeah, so the CS50 library. So from cs50 import-- it looks like get_int is the one we want this time. Now, how do I go about getting an int? Or what's the translation of line 9 on the left to the right-hand side of the screen? x equals get_into of the same prompt. OK, what comes next, if I line it up roughly here? y gets get_int of quote, unquote, y. And what's down here? The condition. So if x less than y? No parentheses are necessary. It's not wrong to put them, but it's unnecessary. And now enters a word into our terminology-- it's not Pythonic, so to speak. If you don't need them, don't put them. So if x is indeed less than y, what do we want to do? We want to print x is less than y, yes? No. OK. All right, good. So else if x-- OK, good. Right. So, kind of goofily, elif, then go ahead and print out x is greater than y. And as an aside, I actually did that accidentally. But it turns out in Python, too, you can use double quotes or single quotes. Either is fine, whereas in C, single quotes had a very specific meaning, which meant what? AUDIENCE: Char. DAVID MALAN: Char. So single characters. And double quotes meant strings, sequence of characters, which meant zero or more characters, followed by backslash 0. In Python, all of that is gone. Single quotes and double quotes are equivalent. I'll almost always use double quotes, just for consistency, as should you, for consistency, within your own files. But sometimes it's useful to drop into one or the other if you nest, for instance, quote marks, as you might have once in a while in C OK. So finally, else print out x is equal to y. So it's cleaner. And frankly, I don't need all this whitespace. So let's go ahead and just make this a little tighter still. You can see that in 11 lines, we've now done what took 27 or so last time. But I have omitted something, to be fair. What did I omit? Yeah, I didn't do that whole calling of function thing. There's no mention of main. And it actually turns out that's not strictly necessary in Python. If you're going to be interpreting a file that contains Python code, and it's a simple enough program that you don't really need to factor code out and organize it into separate functions, then don't. If this is what would now be called a command-line script, a program that just has lines of code that you can execute, literally, at the prompt. So if I go into this directory and run python of conditions.py, Enter. x will be 1. y will be 2. x is indeed less than y. And that's it. I don't need to bother doing all of this, as I proposed earlier. def main, and then I could go in here. And if you've never known this, and now it's useful, especially, for Python, you can highlight lines and just tab them all at once. I could do this, but then I would need this thing, which I'd probably have to go look up how to remember it, if you're doing it for the first time. There's just no value in this case to doing that. But at least it can be there as needed. So let me go ahead and undo that. And we're back to a porting of one to the other. All right. So that might then be conditions. And let's see if we can't-- noswitch there. Let's take a look at this one. Let me open up, rather than comparing all of them side-by-side, let me just open up this one now called noswitch.py, which is reminiscent of a program we ran some time ago called noswitch.c. And you can perhaps infer what this does from the comments alone. What does this program do in English? Because logical operators is not all that explicit at top. What's that? Yeah. So if you've ever interacted with a program that asked you for a prompt, yes or no, here's some code with which you might implement it. And we could do this in C. We're just comparing characters here. But there's a few differences if you kind of now think back to how you might implement this in C, even if you don't recall this specific program. I'm importing my library right up here. I'm then calling get_char this time, which is also in CS50's library for Python. And then notice there's just a couple of things different down here syntactically. Besides the colons and the indentation and such, what else is noteworthy? Yeah. Yeah. Thank god, you can just say more of what you mean now. If you want to do something or something, you literally say or. And if we were instead-- albeit nonsensically here-- trying to do the conjunction of two things, this and that, you could literally say and. So instead of the two vertical bars or the two ampersands, here's another slight difference in Python. Let's now take a look at another example reminiscent of ones past, this one called return.py. So here's an example where it's actually more compelling to have a main function because now I'm going to start organizing my code into different functions still. So up here, we are importing the get_int function from CS50 library. Here I have my main function just saying x gets get_int. And then print out the square of x. So how do you go about defining your own custom function in Python that's not just main? Well, here on line 11 is how I would define a function called square-- that takes, apparently, an argument called n, though I could call this anything I want-- colon, return, n, star star, 2. So a few new features here. But again, it's no big deal once you just kind of look these features up in a manual or in a class. What is star star probably doing? AUDIENCE: Square root. DAVID MALAN: Not square root. The power of, yeah. So n star star 2 is just n raised to the power of 2. That was not a feature we had in C. So now we get this in Python. And what's this line 12 in green with the weird use of double quotes? Yeah, it's a comment. And it's a different type of comment than we've seen before because in my previous example, I did have a few comments. Recall that just a moment ago, in conditions.py, we had a whole bunch of comments. Prompt the user for x. Prompt the user for y. Compare x and y. So whereas in C we were using slash slash, Python, unfortunately, uses that for floor division, so to speak. So we instead just use the hashtag or the pound sign to specify a line that should be thought of as a comment. But here is something a little different. And we won't dwell too much on this for now. But Python has different types of comments, one of which is this. This is technically called a docstring or document string. And what's nice about Python, as well as languages like Java and others still, is that you can put comments in your code that special programs can read, and then generate documentation for you. So if you ever took AP CS and you ever saw a Javadoc, this was a way of commenting your methods and your code in Java using funky @ signs and other syntax so that if you ran a special command, it could generate a user's manual for all of your functions and tell you or colleagues or friends or teachers exactly what all your functions are, what their arguments are, what their return values are, and all of that. Similarly in Python can you use these funky quote quote quote docstrings to document your function. So whereas in C our style has been to put quotes above the functions, in Python it's going to be to put them as the first line inside and indented within the function. All right. So now let's actually try to port a program from code again, thinking back on week one in C when we had this program here. So there's quite a bit going-- oops, spoiler. Don't look at that. Hopefully, that didn't sink in just yet. So in week one, we had this program in C, get_positive_int. And its purpose in life was to write a program that gets a positive integer from the user, in and of itself not all that interesting. But it was an opportunity to introduce a few things. One, we introduced this line 6 several weeks ago, which is known as a prototype. And what was the purpose of having that function prototype up there? Yeah, you declare the function, but why? Because it's already implemented down here on line 15. AUDIENCE: The way the program runs, it needs to be in order or something like that. DAVID MALAN: Yeah. Because of the way the program's run, and frankly because of how sort of naive or dumb that clang is by design, it does not know that a function exists until it actually sees it. So the problem is that if in C, you have main, inside of which is a call to function like get_positive_int, but it's not implemented until a few lines later, clang is going to be dumb and just not know that it even exists. And it's not going to compile your code. So this prototype, as we called it, is kind of a teaser, a hint, that doesn't implement the whole function. It just shows the compiler its return type and its types and order of parameters so that that's enough information to then just trust that if I just blindly compile main, eventually I'm going to see the actual implementation of the function. So I can compile its bits, as well. So in here, in C, we called get_positive_int, and then we passed in a prompt. We stored it in a variable calle i, and then printed it out. And then to implement this, we used kind of a familiar construct that you've used in other programs. Pretty much anytime you want to prompt the user for input, and you want to keep pestering him or her until they cooperate with whatever your conditions are, you would use the so-called do-while loop. And because the do-while loop, recall, is distinct from the while loop how? AUDIENCE: It runs at least once. DAVID MALAN: It runs at least once, which just kind of makes intuitive sense if you want to prompt the user for something. And then if he or she doesn't cooperate, only then do you want to prompt them again. By contrast with a while loop, it's going to happen again and again and again no matter what, from the get-go. So let's see if we can't now convert this or port this, as people would say, to Python. So here I'm going to go ahead and save a new file called positive.py. And I'm going to go ahead and do everything here in main, as before. So I'm going to ahead and do, let's say, from cs50 import get_int, because I do need that. And then I'm going to go ahead and have my main method here. And then inside of main, just like on the left-hand side, I'm going to do i gets get_positive_int-- positive integer, please. It's going to wrap a little bit now. That's fine. And then I'm going to go ahead and print this, which, recall, is just print an f string where the placeholder is i, although, frankly, this is kind of stupid, to just create a string that has nothing other than the value we want to print. Nicely enough in Python, just print what you want. And so that simplifies that argument. So now it remains to implement get_positive_int, which is going to take some kind of prompt as its input. And notice I'm not specifying the data type of prompt, which is string. I'm not specifying the return type of this function. But both actually do exist underneath the hood. So in the past, to get a variable, I would do something like this, semicolon. But I know I don't need the semicolon. I know I don't need the data type. And this just looks stupid to just put a variable there to need it. You don't need to do this in Python. If you want to use a variable, just start using it. And unfortunately, whereas almost every other feature we've seen in Python thus far kind of maps directly back to a feature in C, Python does not have a do-while. So it has the for-in, and it has while. And maybe it has other things we haven't told you about. But it doesn't have do-while. So knowing that, and knowing only what we've presented thus far, how do we still go about getting an int from the user and ensuring it's positive and reprompting him or her if and only if it's not? Put another way, how would you do this in C if we took away from you the do-while construct? Exclamation points? OK. So we could invert something, maybe, using that logically. AUDIENCE: You can just do a while loop. DAVID MALAN: We could just use a while loop. How? AUDIENCE: So while prompt is less than 1. DAVID MALAN: So while prompt is-- OK, so the prompt is the string we're going to display to the user. So it's not prompt, I think. So maybe i or n, to be consistent with the other side. So you know what? Why don't I-- what about this? What if I just do-- you know what? I know I need a loop. This is by far the easiest way to just get a loop, right? It's infinite, which is not good. But I can break out of loops, recall. So what if I do something like this? What if I do n gets get_int, passing in the same prompt? And then what do I want to do next? I'm inside of an infinite loop. So this is going to keep happening, keep happening, keep happening until-- is positive? So python's not quite that user-friendly. We can't just say that. But we can say what? AUDIENCE: Greater than 1. DAVID MALAN: Greater than-- close. AUDIENCE: Equal to. DAVID MALAN: OK, that's fine. Greater than or equal to one. Then what do we want to do? Break. So it's not quite as cool as, like, a do-while loop, which kind of gives us all these features, though frankly, this was never that pretty, right? Especially the fact that you had to deal with the issue of scope by putting the variable outside. So in Python, the right way to do this would be something like this. Just induce an infinite loop, but make sure you break out of it logically when it's appropriate to do so. And so now if I go ahead and add in that last thing that I keep needing-- so if name equals main, and it's always find to copy-paste something like that, call main. Let me go ahead now and in my terminal window run python of positive.py. And let me go ahead and give it negative 5. How about negative 1? How about 0? Whoops. How about that? How about 0? 1? Hmm. I screwed up. None is interesting. It's kind of our new null, so to speak. But whereas in C, null can, potentially, if used in the wrong way, crash your program, Python might just print it, apparently. Where did I screw up? Yeah, so I didn't return an actual value. And whereas clang might have noticed something like this, Python, the interpreter's not going to be as vigilant when it comes to figuring out if your code is missing something. Because after all, we never said we were going to return anything. And so we don't strictly need to. So what could I instead do here instead of break? I could just return n here. Or I could equivalently do this, and then just make sure I return n here. And another difference in Python, too, is that the issue of scope isn't quite as difficult as it was in C. As soon as I've declared n to exist up here, it now exists down below. So even though it was declared inside of this indentation, it is not scoped to that while loop alone. So either way could we actually make this work. OK, so now let's try to run this again. Positive integer. Negative 1. 0. 1. And now we're actually seeing the number 1. All right. Let me pause here for just a moment and see if there's any questions. No? Yes. AUDIENCE: Do you to call things from the CS50 library individually, or can you just import the entire library? DAVID MALAN: Ah, good question. Do you have to call things inside of the CS50 library individually, or can you import the whole thing? You can technically import the whole thing as follows. If you want access to everything in the CS50 library, you can literally say star. And a star in programming-- well, in many computer contexts, star generally is a wildcard character. And it means anything that matches this string here. This is generally considered bad practice, though. Because if CS50 staff happens to give you functionality or variables that you don't want, you have now just imported into your namespace, so to speak, all of those functions. So for instance, if the CS50 library had public inside of it a variable called x and y and z in addition to functions like get_string and get_int and get_char, your program is now seeing variables x and y and z. And if you have your own variables called x and y and z, you're going to shadow those variables inside ours. And it just gets messy quickly. So generally, you want to be a little more nitpicky and just import what you want. Or, another convention in Python is to not specify it like this, but instead to do import CS50. This does not have the same effect of importing all of those keywords like get_int and get_string into your program's namespace, like the list of symbols you can actually type in. But what you then have to do is this-- you have to now prefix any usages of the functions in that library with the now familiar or more familiar dot operator. So this is just a stylistic decision now. I have consciously chosen the other approach so that initially, you can just call get_int, get_string, just like we did in C. But technically and probably more conventionally would people do this to make super clear this isn't my get_int method. It's CS50's get_int function. OK. Other questions? Yeah. AUDIENCE: Is it good coding practice to do the if __name__ or just-- because you can run hello, world without defining main. Do you really need to do-- DAVID MALAN: Oh, it's a good question. Short answer, no. So I'm showing you this way because you'll see this in various examples online and in programs that you might look at that are open source. Strictly speaking, this is not necessary. If you end up making your own library, this tends to be a useful feature. But otherwise, I could equivalently do this, which is perfectly fine as well. I can still define get_positive int. I can get rid of main altogether. And I can just now do this. So this program is equivalent and just as fine for now. OK. So with that said, let's do a couple of more examples here to kind of paint a picture of some of the things that are similar and different. And let's go ahead and open up, for instance, overflow.c from some weeks ago, splitting our windows again. And then on the right-hand side, let me open up something called overflow.py, which I put together in advance. So here we have on the left an example of integer overflow, whereby if I start counting at 1, and then don't even have a condition, and I just keep multiplying i by 2, by 2, by 2, doubling it, doubling it, doubling it, doubling it, we know from C that bad things happen if you just kind of keep incrementing something without any boundary in sight. So this program is just going to print out each of those values, and it's going to sleep one second in between. Same program in Python looks pretty similar. But notice I'm initializing i to 1, doing the following forever-- printing out i, multiplying i by 2, and then sleeping for one second. But sleep is also not built into Python in the way that print is. Notice what I had to include up here. And I wasn't sure what that was. And so honestly, just a few days ago, I googled, like, "sleep one second Python," saw that there's this time library, inside of which is a sleep function. And that's how I knew which library to actually include. And so just as there are man pages for C, there's a whole documentation website for Python that has all of this information, as well. So let me go ahead and do this. And let me actually try to create two windows here. What's the best way for me to do this? Split one to two. OK. So let's do this, just so I can run this in the same place. So if I go into my source-- [POPPING NOISE] Jeez. My source 8 directory, and I go into weeks and one, and I make overflow-- nope, sorry. Week one. OK. So if I go into source one, and I do make overflow, which is kind of cute semantically, I'm now going to be able to run a program called overflow. Meanwhile, over here, let me go ahead and split this window, too. Dammit, not there. Let's put this over here. Oh, no! OK. One second. Sorry. Overflow.py. OK. So now we're-- oh, now I lost the other window. Oh, that's cool. OK. So let's do this. OK. Now I know how to use the IDE. All right. So on the left-hand side, I'm about to run overflow. And then lastly, without generating that beep again, I'm going to go in here. And I'm about to run python of overflow.py. All right. And so the left will run the C version. The right will run the Python version. And we'll start to see-- no pun intended-- what happens with these programs. Oh, damn it. I got to scroll. OK, so I'll just keep scrolling for us. This is fun. OK. OK. Next time, Google how to sleep for half a second instead. OK. So there we go. Something bad has happened here. And now C is just completely choking. Things are in a funky state. So what happened on the left, before the answer scrolls away? Integer overflow, right? We had so many bits becoming ones, that eventually, it was mistaken for a negative number temporarily. And then the whole thing just kind of got confused and became permanently zeros. Whereas on the right-hand side, like, yeah, Python. Look at you go. Still counting higher and higher and higher. And even though we haven't talked about the underlying representation of these types in Python, what can we infer from the apparent better correctness of the version on the right in Python? It's not an eight-bit representation. And even C, to be fair, uses 32 bits for its ints. And that's what we got as high as 2 billion or 4 billion in total. But same idea. How many bits must Python be using? AUDIENCE: 64? DAVID MALAN: Yeah, maybe 64. I don't know exactly. But I know it's not 32 because it's keep counting up and up and up. And so this is another feature of Python. Whereas int in C has typically been for us 32 bits-- although that is technically machine-specific-- Python integers are now going to be 64, which just means we can do much bigger math, which is great for various data-science applications and stats and whatnot, where you actually might have some large data sets to deal with. Unfortunately, we still have some issues of imprecision. Let me go ahead and close a whole bunch of these windows and go ahead and open up, for instance, just this one here. OK. No, I'm going to skip this and do something slightly more fun, which is this. So in Python here, let's do a quick warm-up. This is going to print for me what? AUDIENCE: Four question marks. DAVID MALAN: 4 question marks, right? And this is reminiscent-- this is like a really cheap version of "Super Mario Bros." And if you think back to week one, where we explored this, there was a screenshot I had of "Super Mario Bros," one of the worlds where we just had four question marks which Mario could hit his head against to actually generate a coin. So we stepped up from there in C to do this instead. And this is going to give us another feature. But let's see if we can't start to infer from context what these programs do. Here's another one, mario1. What's this do? It's using a loop, for sure. And it's using how many iterations, apparently? Four. So from 0 to 1 to 2 to 3, total. Each time, it's going to print out, apparently, a question mark. But now, just infer from this-- I haven't answered this question already-- what else is going on line 4 and why? AUDIENCE: It's not going to a new line. DAVID MALAN: Not going to a new line, right? So there's always this trade-off in programming and CS more generally. Like, yay, we took away the backslash n, which was annoying to type. But now if it's always there, how do you turn it off? So this is one way to do that, and it also reveals another fundamental feature of Python. Notice that print apparently takes, in this case, more than one argument. The first is a string-- literally quote, unquote, and a question mark. The second is a little funkier. It's like a word, end. It's then an equal sign, and then it's a quote mark. So what is this here? So it turns out Python supports what are called named parameters. So in C, any parameters you pass to a function are defined, ultimately, by way of their order. Because even if a function takes arguments that have names, like x and y or a and b or whatever, when you call the function, you do not mention those names. You know they exist, and that's how you think about them in the documentation or in the original code. But you don't name the arguments as you pass them in and call a function. You instead pass them in in the appropriate order per the man page or per the documentation. In Python, you can actually be a little more flexible. If a function takes multiple arguments, all of which have names, you can actually mention the names explicitly, thereby freeing you from the minor inconvenience of having to remember and always get right the actual order of arguments. So in this case, print apparently takes at least two arguments in this case, one of which is called end. And if you want to use that one, which is clearly optional because I haven't used it yet, you can literally mention it by name, set an equal sign, and then specify the value that you want to pass in. So if I actually now go into this and go into weeks and 1 and do python of mario1.py, I'll still get-- in week two. If I get mario1.py, I still get four question marks. But that's the result of printing this with a line ending of quote, unquote. If I do this, meanwhile, it's a little stupid because I'm going to get that for free if I just omit it altogether. But now I get four question marks here. And if you really want to be funky, you can do something like this, which is just going to be taken literally to give you that instead. Unclear utility of taking this approach. But that's all-- [POPPING NOISE] Sorry-- that's going on. Let's take a look at mario2. This one works a little differently, as well. And how would you describe the feature offered by this version of mario? Prints any number of question marks perfectly. So it's parameterized by first getting and int from the user using CS50's get_int function. And now I'm iterating from i to the range of n, whatever that is, and then actually printing out the question marks. Meanwhile, in mario3.py, a little fancier still. But what am I doing a little better now? AUDIENCE: You're making sure that the n is positive. DAVID MALAN: Yeah, I'm just making sure that the n is positive. So I didn't bother implementing a whole function called, like, get_positive_int. I don't need that. This is a super-short program. I'm just using the same logic up here-- inducing, deliberately, an infinite loop, breaking out of it only when I've gotten back a positive integer, and then printing out that many of hashtags, reminiscent of the bricks in Mario. And then lastly, we have this slightly more sophisticated version that actually prints out a different shape altogether. You can infer from the comments, but focus more on why. So this first line 12 iterates from i to n, whatever n is that the user typed in. Meanwhile, line 15, indented, iterates from j from 0 up to n, as well. So this is kind of like our canonical for int i gets 0, dot, dot, dot, for int j get 0, dot, dot, dot, where we've had nested loops in the past. So notice now that we have this building block, which is a line of code or kind of conceptually just a Scratch piece. We can embed one inside of the other. Here I can print out a hashtag, making sure not to put a new line after every single hashtag I print out, only printing out a new line on line 17, on each iteration of the outer loop. And now notice whereas in C we would have done this historically-- and that's fine-- in Python, we don't need the f. And we also don't need the backslash n. End So ergo, you can simply do print, and you'll get, if nothing else, a backslash n automatically. So that now when I run this version of Mario, we now get something more interesting. And I'll increase the size of my terminal window for this so that I can enter a positive number like this and print 10. And now we've got a whole block. So that was a lot. Let's go ahead and take our five-minute break here. And when we come back, we'll look at some more sophisticated examples still. All right. So let's begin to start to transition to actually solving problems with Python after introducing just a couple of additional features that aren't so much syntactic but actual features of the language. So here on the left was an old program we wrote in week three called argv0.c. And its purpose in life was simply to allow you to run a command-line argument for the very first time. And that was a nice tool to have in our toolkit. So how might we go ahead and map this? Well, we actually need to know how Python works a little bit differently as follows. If I go ahead and open a new file called-- let's call it argv0.py. I'm going to go ahead and translate this, just as we did earlier. So I'm going to go ahead and want to use the following. So if argc-- so there is no argc. And actually, so def main-- there was also no argc or argv. And it's not actually correct to do this and this, as you might assume. It turns out that the feature of command-line arguments are provided by a Python package, so to speak, or a library, much like the CS50 library is a package that you can import in Python speak. So to do this, I actually need to do this-- import sys, which gives me access to a whole bunch of system-related stuff, like what the user has typed at the command prompt. And if I want to check if the number of words that the human typed at the prompt is two, I actually am going to do this-- if the length of sys.argv equals 2, then I'm going to go ahead and print out quote, unquote, "hello," and then a placeholder here. I know for placeholders, I need to turn this into a formatted string, so an f string there. And now inside of the curly braces, it turns out I can do sys.argv[1]. So it's a little different from before. But notice I'm borrowing almost all the same ideas as earlier, including how we're printing out strings. And even though this is a little more verbose, what is between these two curly braces? Well it's the result of looking in the system package, which has a variable called argv, for argument vector, just like in C. It is itself an array, AKA a list in Python. And here we have the result of indexing into element one of that list. And the way that I have access to this is because I've imported that whole package. So if on the right-hand side here I go ahead, after saving that file, and I do python of argv0.py, I see nothing. But if I actually say, like, my name here, I see, "hello, David." So a very similar program, but implemented a little differently. And you'll notice, too, that the length of an array, henceforth known as a list, is not something that you yourself have to remember or keep around. You can just ask a list how long it is by calling the len-- or L-E-N for length-- function, passing it in as an argument. So that's one of the takeaways there. And if we actually want to do something a little more clever, like print out all of the strings in argv, well, back in the day in see, you might recall this example-- argv1.c, wherein I had this for loop. And I iterated from zero on up to argc, the argument count, printing out each of the arguments in that vector. Python actually makes even something like this even simpler. Let me go ahead and create a new file here. And I'll call this, say, argv1.py. And it turns out in Python, I can similarly just import sys, and then do, honestly, for s in sys.argv, print s. Done. So again, kind of just says what it means. So I've imported the system library. sys.argv I know to be a list, apparently, of command-line arguments. For something in something is a new syntax we have for for loops. So for some variable s inside of this list, go ahead and print it. And so it's a much cleaner, much more succinct way of honestly getting rid of all of the complexity of this by just saying instead what we mean. Meanwhile, if I wanted to print out every character, I can take this one step further. So back in the day in C if I wanted to print out every command line argument and every character therein I could do this. I just need a couple of nested loops, wherein via the outer loop, I iterate over all of the arguments passed in. And on the inner loop, I iterate over the current string length of whatever argument I'm printing. And this had the effect of printing out all of the command-line arguments' letters one at a time. I can do this in Python, honestly, so much easier. So let me go over here. Let me create a new file called argv2.py. Let me import sys, as I did. So import sys. And then for s in sys.argv, for c in s, print c. Done. So what is this doing? Gone is all of that overhead of for int i and for int j and so forth. For s in sys.argv iterates over all of the elements of that list, one string at a time. For c in s is a little different because s is technically a string or a str object, as we're going to start calling it. But at the end of the day, a string is just a sequence of characters. And it turns out Python supports, out of the box, the ability to use a for loop even to iterate over all of the characters in a string. And so c-- I just mean char. So for c in s, that gives me each of the characters. So now at the end here, if I go ahead and run python of argv2.py with nothing, I get just the program's name because that's, of course, the very first thing in argv, as in C. And if I write, say, a word like "Maria" here, I get argv2.py, Maria, all in one long column because of the additional prints that are happening and the implicit new lines. So any questions before we proceed on this use of a package called sys using these functions therein? All right. So let me skip ahead, then, to something slightly familiar, too. Let me go ahead-- and you might recall initials.c from some time ago, wherein we accepted as input to get_string a user's name, and then we printed out their initials. So let's go ahead and do that. So from CS50, let me go ahead and import get_string. Then let me go ahead and say, get me a string. And I want the user to be prompted for their name, as we might do here. Then let me go ahead and say, all right, their initials-- I don't know what they are yet. So let me just initialize an empty string. But then do this-- for c in s, which is for each character in the person's name, if-- and I don't know how to say this yet. If c is an uppercase letter, then go ahead and append c to initials. And then down here, print initials. So I've left a couple of blanks. That's just pseudocode for the moment. But this line 5, just to be clear, is doing what for me? What is being iterated over? The string. So for each character in the string, for c in s, I'm going to ask two questions. So in C, we did this in a couple of different ways. We can actually do it with inequality checks and actually considering what the underlying ASCII values are. The ctype library had that isupper function and islower that we use. Well, it turns out because c is itself not a char, there is no such thing, technically, as a char in Python. You have only strings of length 1. And this is why single quotes no longer have any special meaning. It turns out c is technically just a one-character string. Strings are what we've started calling objects, which is a fancier name for struct. So inside of an object like a string is functionality. And we saw one piece of functionality earlier, which was what? Not length, though that is another one. It was format. We saw it briefly. But when I did the string.format, I proposed that there's actually built-in functionality to a string called format. Well, you know what? It turns out there is a method or function inside of the string class also called isupper. And I can ask the very string I'm looking at that question by saying if c.isupper is true, then go ahead and append c to initials. So in C, if initials were technically a string, how could you go about appending another character to a string in C? AUDIENCE: c.append? DAVID MALAN: Not in C. In C. So in C, the language. OK, so what's a string in C? A string in C is a sequence of characters, the last one of which is backslash 0. All right. So it's an array of characters, last of which is backslash 0. So if I, for instance, typed in my first name, "David," and now I want to append "Malan" to the end of it, how do I do that in C? AUDIENCE: [INAUDIBLE] DAVID MALAN: Exactly. It's like an utter pain in the neck. You have to create a new array that's bigger, that can fit both words, copy the "David" into the new array, then copy the last name in, then put the null terminator at the new array, then free, probably, the original memory. I mean, it's a ridiculous number of hoops to jump through. And you've done this on occasion, especially for things like, perhaps, problem set five. But my god, we're kind of past that. Just go ahead and append to the array the character you care about. So in this case, not an array, but a list. Sorry, not an array but a string object that's initially blank. It turns out that Python supports this syntax-- plus equals typically means arithmetic and add one number to another. But it also means append. So you can simply append to initials by doing plus equals c, one additional character. So even though the string starts like this, and this big in memory, it's then going to grow for one character, grow, grow, grow, grow, until it has all of the user's initials. And as for where that memory is coming from, who cares? This is the point that we're now past. You leave it to the language. You leave it to the computer to start to manage those details. And yes, if it needs to call malloc, fine. Do it. Don't bother me with that detail. We can now start thinking and writing code sort of conceptually at this level, instead of at this level. So again, we're sort of abstracting away what a string even is and leaving it to the language itself. So if I now go ahead and run python of initials.py and type in, for instance, "Maria Zlatkova" here, with a capital M and a capital Z, I then see her names because I've plucked out the middle initials. And if we do something else, like "David J. Malan," even with a period in there, it infers from the capitalization what my initials should actually be. So again, a much tighter way of doing things. Let me go ahead and now open up another example we didn't see a few weeks ago, though it was included in some of our distribution code, if you wanted to look. Some weeks ago, we had this program among the distribution code, where I declared an array of strings called book. And I proposed that there were these several names in the phone book, so to speak-- all of the past instructors of CS50 sorted alphabetically. And then down below in this C program, I used that global variable called book to implement, it seems, linear search. And to implement linear search in C, I'm going to need, of course, a loop to iterate over all of the strings. This line 26 does exactly that. I then in C, recall, had to use str compare because remember we tripped over this issue early on where you can't just compare two strings in C because you'd be comparing, accidentally, their addresses, their pointers, not the actual value. So we used str compare. And I could pass in the name that I'm looking for in the i'th book one at a time, checking for equals zero. And then I can call Mike or David or whoever I'm trying to call, or just quit if the user isn't found. So what did this program actually do? If I go into this example, which, again, was from weeks 3, and I do make linear-- nope, not that. Wrong directory again. If I go into source3 and make linear, this program is supposed to behave as follows. So if I go ahead and run ./linear, look for our old friend Smith, it found Smith. If I go ahead and search for, say, Jones, who did not previously teach CS50, it says "quitting." All right. So meanwhile, in Python, bless its heart, we can get rid of all of that. And in our source8 directory here and our subdirectory 3, let me go ahead and open this instead. In Python, I can declare an array, otherwise known as a list, almost in the same way. But what's different, just to be super clear? AUDIENCE: Brackets? DAVID MALAN: So the brackets are now square brackets instead of curly braces. And frankly, unless you statically initialized an array in C-- like hardcoded the values for your array in C-- you might not have even known you could use curly braces. So that's not a huge deal here. But in Python, square brackets here and here represent a list of elements, literally. And what else is different? Didn't declare the size of the array. And I technically don't have to do that in C, either, if you're hardcoding all of the values all at once. But there is something missing on line 7. AUDIENCE: Type. DAVID MALAN: Sorry? AUDIENCE: The type? DAVID MALAN: The type. I didn't specify string. But otherwise, this is pretty similar to what we've done in C. But what's beautiful here-- and let me go ahead and hide that for just a second. Let me go ahead and prompt the user for his or her name. So let's ask for the name here. And then if I want to search the book, which is just a list of names, how do I implement linear search? Well I could just do if name in book, print "Calling name." And Let's make this an f string. And then down here, that's it. So that's how you implement linear search in Python. You don't need a loop. You can just ask the question yourself. So if book is a list, and name is the string that you're looking for, just ask the language to figure this out for you. If name in book is the syntax you can use to ask literally that question. And then Python will use, probably, linear search over that list because it doesn't necessarily know it's sorted, even though it happens to be alphabetically. But it will find it for you, thereby saving us a lot of the complexity and time of having had to implement that ourselves. Meanwhile, if I want to compare two strings, let me propose this-- let me write a quick program here, compare1.py. And let me go ahead and from CS50 import get_string, as before. And now let me go ahead and get one string that I'll call s. And let me get another string that I shall call t, just as we did a few weeks ago. And now in C, this was buggy, right? If I print same, else I print different. So in C, just to be super clear, why was this incorrect, this general idea of using equals equals? Yeah, they're comparing addresses, right? This was like the day before we peeled back the layer of what a string actually is. And it turns out that s and t in C were char stars or addresses, which means certainly if you get two different strings, even if you've typed the same characters, you're going to be comparing two different addresses. They're not going to be the same. Now, you can perhaps infer from the theme of today-- what is Python going to do if asked if s and t are equal? It's gonna answer that question as you would expect as the human. Equals equals now, in Python, is going to compare s and t, look at their actual values because they are strings, and return same if you literally typed the same words. So in here, if I go in here and I do python of compare1.py, and I type in, for instance, "Maria," and then I type in "Maria," they're indeed the same. If I type in "Maria" and, say, "Stelios," they're different because it's actually now comparing the strings, as we would have hoped some time ago. So let's take a look at another that kind of led to some interesting quandaries. You might recall in week four, we had this example in C-- noswap, so named because this just did not work. It was logically seemingly correct. But swap did not actually swap x and y, but it did swap a and b. Why? AUDIENCE: The memory locations? DAVID MALAN: The memory locations were different, right? So x and y, recall, are variables in C that exist in a certain slice of memory that we called a frame on the stack, main's frame on the stack. Meanwhile, a and b are from a slightly different location in memory. We sort of kept drawing it slightly above, like a tray at the dining hall on the so-called stack. a and b had the same values of x and y-- 1 and 2-- but their own copies of them. So even though we logically, as with Kate, I think, with the Gatorade, swapped the two values, we ultimately swapped the wrong two values without actually permanently mutating the original x and y. So unfortunately-- well, fortunately and unfortunately in Python, there is no such thing as a pointer. So those are now gone. So we no longer have the expressiveness with which to solve this problem that way. But let me propose that we do it in oh-so-clever of another way. Here let me go ahead and declare x is 1, y is 2. Let me go ahead and print out as much. So with a format string, I'm going to go ahead and say x is x, y is y, plugging in their respective values. I'm going to do that twice. But in between, I'm going to try to perform this swap. And if your mind's ready to be blown, you can do that in Python, do the old switcheroo in Python. And this actually does swap the two values as you would expect. Now this is not a very common case. And to be fair, this is an incredibly contrived example because if you needed them swapped, well, maybe you should have just done this in the first place. But it does speak to one of the features of Python, where you can actually do something like that. Let me introduce now one additional feature that we only recently acquired in C. And that's the notion of a struct. And let me go ahead and do this in code from scratch. So let me go ahead and save this file proactively as struct0.py, reminiscent of one of our older programs. And let me go ahead and do this. From CS50 import get_string. And then let me give myself an empty list. So that would be a conventional way of giving yourself an empty list in Python. And much like in C, you can declare an empty array. But in C, you have to know the size of it or, if not, you have to use a pointer. And then you have to malloc. No. All of that is gone. Now in Python, you want a list? Just say you need a list. And it will grow and shrink as you need. Now I'm going to go ahead and just three times, arbitrarily, for i in the range of 3, let me go ahead and ask the user for a name using get_string. And I'll ask him or her for their name. Dorm will use get_string, as well. Dorm here. And then I want to append to the array this student. So I could do something like this-- students.append name. And it turns out-- and we've not said this yet. But there is inside of the list data type a method-- that is function-- built into it called append that literally does that. So if you've got an otherwise empty list, and you calls that list's name dot append, you'll add something to the end of the list. And if there's not enough memory for it, no big deal. Python will find you the memory, allocate it, move everything in it, and you move on your way without having to worry about that. But I don't want to store just the name. I want to store the name and the dorm. So I could do this. I could do-- well, maybe this isn't really students. Maybe this is now, like, dorms. And then here I could do dorms.append dorm. But why is this devolving now into bad design if my goal was to associate a student with his or her dorm, and then keep those values together? Why is this not the best approach in Python or, back in the day, even in C, to have two separate arrays? AUDIENCE: Like struct? DAVID MALAN: What's that? AUDIENCE: Struct? DAVID MALAN: Well, it's twice as many things to maintain, for sure. And what else? AUDIENCE: You can't map them to each other. DAVID MALAN: You can't map one to the other. It's just-- it's very arbitrary. It's sort of this social contract that I will just assume that student 0 lives in dorm 0. And student 1 lives in dorm 1. And that's fine. And that's true. But one of the features of programming and computer science is this idea of encapsulation, like, associate related memory with each other. And so what did we do in C instead? We did not have two arrays. AUDIENCE: We had a struct. DAVID MALAN: Yeah, we had a struct. And so Python doesn't have structs per se. It instead has what are called classes. And it has a few other things like tuples and namedtuples, but more on those some other time. So it turns out I could actually implement my own notion of a student. And I could import it like this. The convention in Python is if you create your own struct, henceforth called a class, you capitalize the name of it by convention. So a little different from C conventions. So what is a student going to look like? This is perhaps the most complex syntax that we'll have today, but it just has a few lines. If you want to implement the notion of a student, how might you do this? Well, in Python, you literally say class Student, where class is similar in spirit to-- just to be clear-- struct or typedef struct. But in Python, we're just saying class. And then this is the funky part. You can declare a function that by convention must be called init for initialize that takes as its first argument a keyword called self, and then any number of other arguments like this. And then, for reasons that will hopefully be clear momentarily, I can write some code inside of this method. So long story short, what am I doing? I have declared a new type of data structure called Student. And implicitly inside of this data structure, there are two things inside of itself-- something called name and something called dorm. And this is how you would in a C struct typically do things with the data types and semicolons inside of the curly braces. Meanwhile, there's this method here. And it's a method insofar as it is inside of a class. Otherwise it's a function, just by a different name. This method init takes whatever self is-- more on that another time. But it then takes zero or more custom arguments that you can provide. And I called it name and dorm. So it turns out this special method init is a function that's going to be called automatically for you any time you create a student object. So what does that actually mean? That means in your code, what you can actually do is this. I can create a student in memory by saying s gets capital Student, passing in name and dorm. And we don't have this feature in C. On the right-hand side, what I've highlighted is the name of the class and its two arguments-- name and dorm, which are just what the user has typed in. What this class does for me now is it allocates memory underneath the hood for a Student. It's got to be big enough for their name and big enough for their dorm. So it's, like, yay big in memory, so to speak. It then puts in the name and the dorm strings into that object, and then returns the whole object. So you can kind of think of this as a much fancier version of malloc. So this is allocating all the memory you need. But it's also installing inside of that memory the name and the dorm. And it's bundling it up inside of not just an arbitrary chunk of memory, but something you can call a Student object. And all that means that now for our students, we can just go ahead and append that student to the list. So now if later I want to iterate over for student in students, I can go ahead and print out, for instance, that student.name lives in student.dorm, close quote. And if now over here-- whoops, close that. Now over here, if I go ahead and run python on struct0.py-- oh, no! Oh, thank you. That goes there. So now-- dammit. Missing curly-- oh, thank you. OK. So now if I want to go ahead and type "Maria" and "Cabot" and "David" and "Mather" and "Rob" and, say, "Kirkland," now we get all three of those names. And there's other ways, too, if we wanted to actually store this thing on disk. But I'll defer that to an example online. Let's look at one final example that will hopefully either make you regret the past several weeks or embrace the next several instead. So you'll recall that-- though the former, I suppose, could be true even without my help. So if I go into now today's distribution code, you will see this program. And we won't walk through all of its lines. But this is a program written in Python called speller. And what I did was literally sit down with speller.c from problem set 5. And I just converted it from left to right, from C to Python, implementing it in Python in as close to an identical way as I could, just using features of Python. So just skimming this, you'll see that apparently my implementation of speller in Python has a class called Dictionary which is very similar in spirit to dictionary.h in C Notice that I still have a constant here. Or it's not technically a constant, but a variable called length equals 45. I hardcoded in dictionary/large, as speller.c did, too. I'm using command-line arguments, as we saw earlier, but this time in Python instead of C. Notice you can do funky things like this, which is reminiscent of our swap trick just a little bit ago. If you want to declare multiple variables all on the same line and initialize them, you can just enumerate them all with commas. Then on the other side of the equal sign, enumerate with commas the values that you want to assign to those variables. And then down here, if I keep scrolling, you'll see code that we won't get into the weeds of, but some familiar phrases. So this is the program that actually runs a student's dictionary on some input, and then prints out per all of this stuff at the bottom all of the familiar phrases that you might recall from problem set five. So this took a lot of work, most likely, to implement in C. And understandably, you might have used a linked list initially, or ultimately you might have used a hash table or a try or struggled with something in between those two. And that is a function of C. C is difficult. C is challenging because you have to do everything yourself. An upside, though, of it is that you end up getting a lot of great performance, theoretically. Once you have implemented the code, you're kind of as close to the hardware as possible. And so your code runs pretty darn well and is dependent only then on your algorithms, not on your choice of language. So here let me go ahead and implement a file called dictionary.py. And let me propose that the words-- the equivalent, sorry, of dictionary.h would be this file here. And it's going to have a function called check, which takes in an argument called word. It's going to have a function called load, which takes in an argument called dictionary. It's going to have a method called size, which takes in no arguments other than itself. And then it's going to have a method called unload, which also takes no arguments other than itself. So if we were instead to have assigned problem set five in Python, we essentially would have given you a file called dictionary.py with these placeholders for you because recall in pset five, those were all to dos. Strictly speaking, there would be one other here. We would probably have a def init because every class in Python, we'll see, we'll typically have this init method, where we just are able to do something to initialize the data structure. So let me go ahead and do this. We don't know that much Python yet. And we're taking for granted that speller in fact, works. But let me go ahead and load some words in a dictionary. So here is my method called load. Dictionary is going to be the name of the dictionary to load. So you guys implemented this yourself by loading those files from disk. In Python, I'm going to do this as follows. Give me a file and open it in read mode. Iterate over each line in the file. Then go ahead and add to my set called words the result of that line by stripping off the end of it backslash 0. Then go ahead and close the file, and then return true because I'm done implementing load. So that is the load method in Python. Happy, yes. OK. So check. Check was a struggle, too, right? Because once you had your hash table, or once you had your try, now you had to actually navigate that structure in memory, maybe recursively, maybe iteratively, following lots of pointers and the like, following a linked list. How about I just do-- let's just say if word lowercase in self.words, return true. Else return false. Done. So that one's done. Size-- we actually can kind of infer how to do this. Return the length of the words. That's done. Unload-- don't have to worry about memory in Python, so that's done. And there you have a problem set five. [APPLAUSE] Thank you. So what then are the takeaways? Either great elation that you now have this power or great sadness that you had to implement this first in C. But this was really ultimately meant to be thematic. Hopefully moving forward, even if you struggled with any number of these topics-- linked lists and hash tables and pointers and the like-- hopefully you have a general understanding of some of these fundamentals and what computers are doing underneath the hood. And now with languages like Python and soon with JavaScript and SQL, with a little bit of HTML and CSS mixed in for our user interfaces, do you have the ability to now solve problems, taking for granted both your understanding of those topics and the reality that someone else has now implemented those concepts for you so that when it comes to solving problem sets six and seven and eight, and then leaving CS50 and solving problems in your own domain, you have so many more tools in your toolkit. And the goal really for you is going to be to pick whichever one is most appropriate. So let's adjourn here. I'll stick around for questions. And we'll see you next time. Best of luck on the test.