CS50P - Lecture 9 - Et Cetera

202.99k views28115 WordsCopy TextShare
CS50
This is CS50P, CS50's Introduction to Programming with Python. Enroll for free at https://cs50.edx.o...
Video Transcript:
so [Music] [Music] all right this is cs50s introduction to programming with python my name is david malen and over these past many weeks have we focused on functions and variables early on then conditionals and loops and exceptions a bit of libraries unit tests file io regular expressions object-oriented programming and really etc and indeed that's where we focus today is on all the more that you can do with python and programming more generally beyond some of those fundamental concepts as well in fact if you start to flip through the documentation for python in all of its
form all of which is as always accessible at docs.python.org you'll see additional documentation on python's own tutorial in library it's reference it's how to and among all of those various documents as well as others more online you'll see that there's some tidbits that we didn't quite touch on and indeed even though we themed these past several weeks around fairly broad topics that are rather essential for doing typical types of problems in python it turns out there's quite a number of other features as well that we didn't necessarily touch on that didn't necessarily fit within any
of those overarching concepts or might have been a little too much too soon if we did them too early on in the course and so in today our final lecture will be focus really on all the more that you can do with python and hopefully wet your appetite for teaching yourself all the more too for instance among python's various data types there's this other one that we haven't had occasion to yet use namely a set in mathematics a set is typically a collection of values wherein there are no duplicates so it's not quite a list
it's a bit more special than that in that somehow any duplicates are eliminated for you well it turns out within python this is an actual data type that you yourself can use in your code and via the documentation here might you be able to glean that it's a useful problem if you want to somehow automatically filter out duplicates so let me go ahead and go over to vs code here and let me go ahead and show you a file that i created a bit of in advance whereby we have a file here called houses.pie and
in houses.pie i already went ahead and whipped up a big list of students inside of which is a number of dictionaries each of which represents a student's name and house respectively now this is a pretty sizable dictionary and so it lends itself to iteration over the same and suppose that the goal here works quite simply to figure out well what are the unique houses at hogwarts in the world of harry potter it would be nice perhaps to not have to know these kinds of details or look them up online here we have a set of
students albeit not exhaustive with all of the houses but among these students here what are the unique houses in which they live well i could certainly as a human just eyeball this and tell you that it's well gryffindor slytherin ravenclaw but how could we go about doing it programmatically for these students as well well let's take one approach first here let me go into houses.pie and let me propose that we first how about create an empty list called houses in which i'm going to accumulate each of the houses uniquely so every time i iterate through
this list of dictionaries i'm only going to add a house to this list if i haven't seen it before so how do i express that well let me iterate over all of the students with for students in students as we've done in the past and let me ask a question now so if the current student's house and notice that i'm indexing into the current student because i know they are a dictionary or dict object and if that student's house is not in my houses list then indented am i going to say houses dot append because
again houses is a list and i'm going to append that particular house to the list then at the very bottom here let me go ahead and do something somewhat interesting here and say for each of the houses that i've accumulated in i could just say houses but if i just say houses what was the point of accumulating them all all at once i could just do this whole thing in a loop let's at least go about and sort those houses with sorted which is going to sort the strings alphabetically and let's go ahead therein and
print each of the houses let me go ahead now in my terminal window and run python of houses dot pi and hit enter and there we have it gryffindor ravenclaw slytherin in alphabetical order even though in the list of dictionaries up here technically the order in which we saw these with was gryffindor gryffindor gryffindor slytherin ravenclaw so indeed my code seems to have sorted them properly so this is perfectly fine and it's one way of solving this problem but it turns out we could use more that's built into the language python to solve this problem
ourself here i'm rather reinventing a wheel really the notion of a set wherein duplicates are eliminated for me so let me go ahead and clear my terminal window and perhaps change the type of object i'm using here instead of a list which could also be written like this to create an empty list let me go ahead and create an empty set whereby i call a function called set that's going to return to me some object in python that represents this notion of a set wherein duplicates are automatically eliminated and now i can tighten up my
code because i don't have to use this if condition myself i think i can just do something like this inside of my loop let me do houses dot add so it's not append for a set it's a pen for a list but it's add to a set per the documentation then let me go ahead and add this current student's house and now i think the rest of my code can be the same i'm just now trusting per the documentation for set in python that it's going to filter out duplicates for me and i can just
blindly add add add all of these houses to the set and any duplicates already there will be gone python of houses dot pi and enter and voila we're back in business with just those three there as well let me pause here to see if there's any questions now on this use of set which is just another data type that's available to you another class in the world of python that you can reach for when solving some problem like this how can we locate an item in a set for example find gryphon door in that set
how do you find an item in a set you can use very similar syntax as we've done for a list before you can use syntax like if gryffindor in houses then and you can answer a question along those lines so you can use in and not in and similar functions as well other questions on set okay what happens if you have a similar house name let's say instead of slithering it is maybe an o instead of an eye will the for loop look throughout each of those um letters in the house name uh it would
compare the string so if slytherin appears more than once but is slightly misspelled or miscapitalized if i heard you right those would appear to be distinct strings so you would get both versions of slytherin in the result however we've seen in the past how we can clean up user's data if indeed it might be messy we could force everything to uppercase or everything to lower case or we could use capitalize the function built into stirs or title case that would handle some of the cleanup for us in this case because the data is not coming
from humans using the input function i wrote the code in advance it's safer to assume that i got the houses right but that's absolutely a risk if it's coming from users allow me to turn our attention back to some of the other features here that we can leverage in python if we dig further into the documentation and read up more on its features well in some language there's this notion of global variables whereby you can define a variable that's either local to a function as we've seen many times or if you put a variable outside
of all of your functions perhaps near the top of your file that would generally be considered a global variable or in the world of python it might be specific to the module but for all intents and purposes it's going to behave for a given program as though it is global however it turns out that if you do this when solving some problem down the line whereby you have multiple functions and you do have one or more variables that are outside of those functions you might not be able to change those variables as easily as you
might think so indeed let me go back to vs code here and in just a moment i'm going to go ahead and create a new file how about called bank dot pi let's go ahead and implement the notion of a bank wherein we can store things like money in various forms and let me go ahead and do this let me go ahead and implement a very simple bank that simply keeps track of my total balance the number of dollars or cents or whatever i might be storing in this bank and i'm going to give myself
a variable called balance at the top which is an integer a set to zero now let me go ahead and define a main function as we often do and inside of my main function let me go ahead and print out quote unquote balance and then print out the value of balance itself passing to print as we've often done more than one argument so that they get uh separated by a single white space and now since i have a main function really setting the stage for doing more interesting things soon let me go ahead and do
our usual if the name of this file equals equals underscore underscore main then go ahead and call main so this is a terribly short program but it's perhaps representative of how you might solve some future problem in python whereby you have a main function that's going to eventually do some interesting stuff and at the top of your file you have one or more variables that are just useful to keep there because then you know where they are and perhaps not just main but other functions can access them as well so let's see when i run
this program python of bank dot pi i would hope based on my own intuition thus far that i'm gonna see that my current balance is zero that is to say even though the balance variable is defined on line one hopefully i can still print it on line five inside of main even though balance was not defined in my main function here we go hitting enter and voila balance 0. so it does seem to work even if you declare a variable in python outside of your functions it appears that you can access it you can read
the value of that variable even inside of a function like main well let's get a little more adventurous now because this program really isn't solving anyone's problems let's go ahead and implement more of a bank like the ability to deposit money into the bank and to withdraw money from the bank thereby giving me some more functions that might very well need to access that same variable let me clear my terminal window here and let me go ahead and pretend for the moment that i have the ability to deposit say 100 or 100 coins whatever the
unit of currency is here and then maybe i want to withdraw straight away 50 of those same dollars or coins and now let me go ahead and just print out at the bottom of main what my new balance should be so that in an ideal world once i've deposited 100 then withdrawn 50 after starting at 0 i'd like to think that my new balance on line 8 should indeed be 50. all right but i haven't implemented these functions yet so let's do that as we've done in the past down here i'm going to go ahead
and define another function deposit i'm going to say that it takes an argument called n for a number of coins or dollars or the like and i'm just going to do this i'm going to go ahead and say balance plus equals n thereby changing the value of n i could do it more verbosely balance equals balance plus n but i'm going to use the shorter hand notation here instead and now let's implement withdraw so define a function called withdraw it 2 is going to take a variable in argument n for number of dollars or coins
and now i'm going to go ahead and subtract from balance using minus equals n as well and i'm still going to call main if the name of this file is main so what have i done i've just added not just one but three functions total all of which apparently need to access balance by printing it incrementing it or decrementing it as we've seen here all right let me go ahead and focus on these three functions here let me go back to my terminal window and run python of bank dot pi and hit enter and wow
seems like we've introduced some number of problems here and what are these problems well unbound local error is perhaps the first time we've seen this one here local variable balance rep reference before assignment and that's a bit misleading definitely confusing right because i absolutely assigned balance of value on the top of my code and indeed if i scroll back up nothing has changed or been lost up there it's definitely been assigned a value and now on line 12 it would seem that when deposit is called i'm just trying to access that variable again so intuitively
what might explain this error message unbound local error what is python telling us there that python can or can't do when it comes to these so-called global variables that are at the top of my file so if you want to change this variable you should write an inside def function main and the global variable unchangeable yeah so if you want to change the value it might need to be local to the function if you are trying to change a global variable though in a function it clearly does not work so it's okay to read a
global variable read meaning access it and print it and so forth but apparently you can't write to a global variable in the same way from within one of these functions all right well maybe the fix is to do this let me clear my terminal window in that error and maybe i could just do this let's get rid of the global variable and let's go ahead and put it for instance inside of main might this now work well let me try this now python of bank dot pi enter that alone did not solve it i still
have an unbound local error this time though it's for a different reason it turns out now that balance on line two is by definition a local variable a local variable is one that exists in the context of a function at least in this case a global variable is the opposite one that does not for instance at the top of my file so here is another distinction in python if you declare a variable in a function like main just as i've done on line two with balance it is indeed local to that function deposit and withdraw
do not have access to that same variable why because it's local to main and so you would think now we're kind of stuck in this vicious cycle well maybe the solution then is to move balance globally so all three functions can access it but clearly where we began as elena noted we can't therefore change it so it turns out the solution to this problem in python is ironically exactly this keyword here it's a little different as you might have seen if you programmed before in other languages but there's indeed a keyword in python called global
that allows you to tell a function that hey this is not a variable that's local to you i mean it to be a global variable that i want you to edit so if i go back to vs code here clearing my terminal window to get rid of that error let me go ahead and undo the change i just made and put balance back at the top of my file but this time what i'm going to do is i'm going to inform my two functions that need to change the value of balance that it is indeed
global by typing global balance again here as well as here global balance i still leave the same lines of code now on lines 13 and 18 that increment and decrement balance but this now use of keyword global is a little bit of a clue to python that oh okay it's not a local variable this is not a bug that you've introduced you mean for me to edit this variable up above so now let me go ahead in my terminal window and run python of bank dot pi i'm hoping to see that my balance is 0
plus 100 minus 50 is 50. and indeed it now is it starts off at zero per my first print statement on line five but it ends up at 50 total at below that on line eight let me pause here to see if now there's any questions on these global or local variables what happens when you declare a variable globally and as in the same variable globally and in a function a good question you're always thinking about the so-called corner cases so if you declare a variable both globally like at the top of your file and
then an identically named variable inside of a function same name the ladder will shadow so to speak the former that is you'll be able to use the latter that is the local variable but it will have no effect on the global variable temporarily python will only know that the local variable exists so in general the rule of thumb is just don't do that not only might it create bugs in your code because you don't quite change what you intend to change it's also perhaps non-obvious to other readers as well other questions on globals or locals
okay what if we decide to add balance as an argument inside the main function yeah another good instinct but in this case that also is not going to solve the problem because if you pass in a variable like balance to each of the functions and then change it within that function it's only going to be changing in effect a local copy thereof it's not going to be changing what's outside of those functions so i think we actually need a better way altogether and in fact allow me to transition to perhaps a modification of this same
program recall that we looked most recently at this notion of object-oriented programming whereby you can model real-world entities for instance a bank and you can model and encapsulate information about that real world entity for instance like someone's account balance so let me propose that we actually do this let me start from scratch with bank dot pi get rid of the global variable altogether and actually use some object-oriented code let me define a class called account to represent someone's bank account and then let me go ahead and initialize with my init method which again takes by
convention at least one argument called self let me go ahead and initialize the every person's bank account to some value like zero now how can i do that well i'm going to go ahead and do self.balance equals zero thereby giving me an instance variable called balance initialized for this account to zero but i'm going to proactively remember how we also introduce this notion of properties which might otherwise collide with the names of my instance variables so just by convention i'm going to do this i'm going to rename this instance variable proactively to underscore balance to
effectively indicate that it's private even though that's not enforced by python it's just a visual clue to myself that this is something that really i should not or other code should not touch just functions in this class now let me go ahead and do this let me go ahead and define an actual function called balance that really is going to be a property whose purpose in life is just to return self self.balance and i'm going to go explicitly and say this is indeed a property of this class now let me go ahead and re-implement those
other two functions deposit and withdraw but in the confines of this class so i'm going to say define deposit it's going to take in an argument self as always but an additional one n a number of dollars or coins to deposit and how do i now manipulate this well i'm going to do self dot underscore balance plus equals n and now down here i'm going to do def withdraw self and just like for deposit but here i'm going to do self.balance minus equals n and now if i go down below this class i'm going to
go ahead and define myself a main function just so i can try this now out i'm going to go ahead and create an account object by calling the account constructor that is the name of the class with two parentheses if i'm not passing in any arguments to in it i'm going to go ahead now and print out as before the balance of my account but to do that i'm going to access the property of that account like this and i'm going to go ahead now and say deposit another hundred dollars or coins with deposit 100
and i'm going to go ahead like before and also now immediately withdraw for whatever reason 50 of the same and now i'm going to print one last time balance followed by account.balance again accessing that property and for this whole thing to work of course i need one of these if name equals equals underscore main then go ahead and call main now before i run this you'll see that it rather escalated quickly i had a very simple goal at hand to implement the notion of a bank and i was able to implement that perfectly fine ultimately
by declaring balance to be global but then to tell each of my functions that it is indeed global but that's not really the best form of encapsulation we have at our disposal now right per our focus on object-oriented programming if we're trying to implement some real-world entity like an account at a bank that's what classes allow us to do and it allows us to solve that same problem perhaps a little more cleanly certainly if we're going to accumulate more and more functions or methods over time so if i didn't make any mistakes here if i
run python of bank dot pi and hit enter now you'll see that it just works just fine because in the world of classes in python these so called instance variables are by definition accessible to all of the methods in that class because we're accessing them all by way of that special parameter self so which way to do it for a reasonably small script wherein you are simply trying to implement a script that has some global information like an account balance that you then need to manipulate elsewhere the global keyword is a solution to that problem
but generally speaking in many languages python to some extent among them using global variables tends to be frowned upon only because things can get messy quickly and it can become less obvious quickly exactly where your information is stored if some of it's up here some of it's in your function so generally the rule of thumb is to use global variables sparingly though technically speaking in python these global variables are technically local to our module if we were indeed implementing a library and not just a program so in short try to use global variables sparingly but
when you do there is a solution to these same problems questions now on globals or our re-implementation of the same idea but using full-fledged object-oriented programming uh i just would like to ask uh what this property does what this property does so if i go back to vs code here you'll see that this was a technique we looked at in our lecture on object oriented programming whereby a property is a instance variable that's somehow protected it allows me to control how it can be read and written so in this case i only have what's called
generally a setter and sorry in this case i only have what's generally called a getter and there's no mention of the word getter here this is just what app property means that function balance will allow me recall to use syntax like this where i can pretend as though balance is indeed with no underscore and instance variable but i can now prevent code like mine in maine from trying to change balance because i do not have a setter i would not be able to do something like account balance equals a thousand to just give myself a
thousand dollars or coins because i have not defined a setter so again per our focus on object oriented programming these properties just allow me some finer grain control some languages allow you to define variables that are so to speak constant that is once you have set a value to them you cannot change the value of that variable and that tends to be a good thing because it allows you to program defensively just in case you accidentally or someone else accidentally tries to modify the value of that variable if you have declared it in some language
as a constant it cannot be changed or usually cannot be changed without great effort unfortunately in python we're again on the sort of honor system here where we have conventions to indicate that something should be treated as though it's constant but that's not actually enforced by the language so for instance let me go back here to vs code and let me create a new file for instance called meows.pi and let's see if we can't implement the notion of a cat meowing on the screen so i'll do code of meows.pi and in meows.pie let me go
ahead for instance and implement a very simple program that just has a cat meowing three times so how about this for i in the range of three go ahead and print out quote unquote meow all right well we've seen in the past how we can clean this up a little bit for instance if i'm not actually using i i might as well pythonically just change the name of that variable to underscore even though that has no functional effect here but here we have this three sort of randomly hard-coded that is typed explicitly into my code
and it's totally not a big deal when your code is only two lines but imagine that this is a much bigger program with dozens or even hundreds of lines and imagine that one of those lines just has a 3 in there somewhere like you're never going to find that three very easily and it's going to be very easily overlooked by you or colleagues or others that you've hard coded some magic value like a three right there in your code so it tends to be best practice not just in python but other languages as well anytime
you have what is essentially a constant like a number three that shouldn't ever change is to at least let it bubble up surface it to the top of your code so that it's just obvious what your code's constant values are and so by that i mean this at the top of this file it would probably be a little clearer to colleagues and frankly me tomorrow after i've forgotten what i did today to define a variable like meows and set it equal to three and then instead of hard coding three here or even lower in a
much bigger program let me just go ahead and pass in that variables value to my loop so that now it's just kind of obvious to me that meows is apparently the number of times to meow and if i ever want to change it the only code i have to change is at the very top of my file i don't need to go fishing around or figure out what's going to break what do i need to change i just know that i can change these constants up at the top the problem though with python is that
python doesn't actually make variables constant it's indeed a convention in python and some other languages to at least capitalize your variables when you want to indicate to the world that you should not touch this it is constant but there is literally nothing in my code preventing me from saying you know what today i feel like four meows instead like that would work in other languages though there's typically a keyword or some other mechanism syntactically that would allow you to prevent line three currently from executing so that when you try to run your code you would
actually get an error message explicitly saying you cannot do that so python again is a bit more on the honor system when it comes to these conventions instead now it turns out there's other types of constants quote unquote that python typically manifests in fact let me go ahead and change this around a little bit let me delete this version of meows and let me introduce again a class from our discussion of object oriented programming like a class representing a cat another real world entity recall that within classes you can have not just instance variables but
class variables that is variables inside of the class that aren't inside of self per se but they're accessible to all of the methods inside of that class here too there's a convention but not enforced by python of having class constant whereby inside of the class you might want to have a variable that should should should not be changed but you just want to indicate that visually by capitalizing its name so for instance if the default number of meows for a cat is meant to be three i can literally inside of my class but outside of
any of my defined methods just create a class variable all capitalized with that same value and then if i want to create a method like meow for instance which as an instance method might take in self as we know and then i might have my loop here for underscore in the range of and now i need to access this the convention would be to say cat.meows to make clear that i want the meow's variable that's associated with the class called cat then i'm going to go ahead and print out one of these meows and now
at the bottom of my code outside of the class let me go ahead and do something like this let me instantiate a cat using the cat constructor notice this is important per our discussion of oop the class is capitalized by convention but the variable over here is is lowercase and i could call it just c or anything else but i kind of like the symmetry of calling it little cat here and big cat so to speak over here and now if i want this particular cat to meow that default number of three times i can
just do cat.meow like this and that method meow is going to per line five access that class constant but again it's constant only in the fact only in the sense that you should not touch that not that it's actually going to be enforced by the language all right let me go ahead then and run this with python of meows dot pi and there it is three of our meows meow meow it turns out that python is a dynamically typed language that is to say it's not strongly typed whereby when you want an int you have
to tell the program that you are using an int you don't have to tell the program that you are using a stir or a float or a set or anything else generally speaking to date you and i when we're creating variables we just give a variable a name we frequently assign it using an equal sign some other value and honestly python just kind of dynamically figures out what type of variable it is if it's quote unquote hello world the variable is going to be a stir if it's 50 the integer the variable is going to
be an int now in other languages including c and c plus and java and others it's sometimes necessary for the programmer to specify what types of variables you want something to be the upside of that is that it helps you detect bugs more readily because if you did intend for a variable to store a string or an integer but you accidentally store an integer or a string the opposite or something else altogether your language can detect that kind of mistake for you when you go for instance to run the program it can say no you've
made a mistake and you can fix that before your actual users detect as much in python 2 here it's again more of a friendly environment where you can provide hints to python itself as to what type a variable should be but the language itself does not strongly enforce these rather you can use a tool that will tell you whether or not you're using a variable correctly but it's typically a tool you would run as the programmer before you actually release your code to the world or if you have some kind of automated process you can
run this kind of tool just like you could reformat or link your code with some other program before you actually release it to the world so how might we go about using these so-called type hints well they're documented in the usual place in python's own documentation and it turns out there's a program that's pretty popular for checking whether or not your code is adhering to your own type hints and that program here is called my pi and it's just one of several but this one's particularly popular and can be easily installed in the usual way
with pip install my pi and its own documentation is at this url here but we'll use it quite simply to check whether or not our variables are indeed using the right types so how can we go about doing this all right let me go back here to vs code clear my terminal window and in fact erase meows.pi as it currently was and let's implement a different version of meows that quite simply has a function called meow that does the actual meowing on the screen and then i'm just going to go ahead and call that function
down toward the bottom i'm not going to bother with a main function just for simplicity so that we can focus as always only on what's new so here we are defining a function called meow it's going to take a number of times to meow for instance n for number and inside of this function i'm going to do my usual for underscore in the range of n go ahead and print quote-unquote meow so based on our earlier code i think this is correct i've not bothered defining the variable as i i'm instead using the underscore because
i'm not using it anywhere but i think i now have a working function whose purpose in life is to meow 0 or 1 or 2 or 3 or more times well let's use this function again not bothering with main i'm just going to keep my function at the very top because there's only one and i'm going to write my code here on line 6. so i'm going to give myself i'm going to ask the user for a number and i'm going to go ahead and prompt them in the usual way for that number of times
to meow and now i'm going to go ahead and call meow on that number now some of you might see what i've already done wrong but perhaps i myself don't so let me go into my terminal window and run python of meows.pi the goal being to prompt me this seems to be working i'm going to type in three and i would expect now the meow function to print out meow three times enter but no there's some kind of type error here stir object cannot be interpreted as an integer why might that be why might that
be because the input function retains a string instead of an integer exactly the input function returns a string or a stir not an in so in the past of course our solution to this problem has just been to convert the string to an int by using the in function but now let me start programming more defensively so that honestly i don't even find myself in this situation at all let me go ahead and do this let me add what's called a type hint to my function that explicitly specifies for meow what type of variable should
be passed in i'm going to go ahead now and change the very first line of my code in my function to specify that n colon should be an int and this is a type hint the fact that i've added a colon a space and the word int is not creating another int or anything like that it's just a hint an annotation so to speak to python that this variable on the left called n should be in int now unfortunately python itself doesn't care because again these type hints are not enforced by the language and that's
by design the language itself in the community prefers that python be dynamically typed not so strongly typed as to require these things to be true but if i run meows.pi type in three again the same error is there but let me go about trying this mypi program an example of a program that understands type hints and if i run it proactively myself can find bugs like this in my code before i or worse a user actually runs and encounters something cryptic like this type error here let me clear my terminal window and this time run
my pi space meows dot pi so i'm going to run my pi on my program but i'm not running python itself when i hit enter we'll see this alright we see now that my pi found apparently an error on line seven error argument one to meow has incompatible type stir expected int so it's still an error message but my pie is not a program that my users would use this is a program that you and i as programmers would use and because we have run this code now before we for instance released this program to
the world i can now see even before the code is called or run oh i seem to be mute using my argument to meow wrong i had better fix this somehow well i can actually go about in hint adding type hints even to my own variables here so as to catch this another way too if i know on line six that i'm creating already a variable called number and i know already that i'm assigning it equal to the return value of input i could give my pi and tools like it another hint and say you
know what this variable called number should also be an int that is to say if i now start getting into the habit of annotating all of my variables and arguments to functions maybe my pi can actually help me find things quite quickly as well before i get to the point of running python itself let's go ahead and try this again my pie of meows dot pie and hit enter and this time notice that my pie actually found the mistake a little more quickly notice this time it found on line six that error incompatible types and
assignment expression has typed stir variable has type int so before i even got to the point of calling meow line six via this type in when used and analyzed by my pi has helped me find a wait a minute i shouldn't be assigning the return value of input to my variable called number in the first place why my pi has just pointed out to me that one returns a stir i'm expecting an int let me fix this now instead all right so let me clear my terminal window and now let me do what most of
you were probably thinking i should have done in the first place after all of these weeks but now let me go ahead and convert the return value of input to an integer for today's purposes i'm not going to try to catch any exceptions or the like we're just going to assume that the user types this in properly and now let me go ahead and run my pi of meows.pi having not only added two type hints to my argument to my function to my variable down here on line six and i've also now fixed the problem
itself let me go ahead and run my pi and success no issues found in one source file now it's more reasonable for me to go and run something like python of meows and just trust that when i type in three at least i'm not going to get a type error that is i didn't mess up as a programmer with respect to the types of my variables why because when i wrote the code in the first place i provided these annotations these hints that inform tools like my pi that my intention had better line up with
what the actual code does let me pause here and see if there's now any questions on tight pins or my pie is it common or how common is it for those to be used or is it just that it's more used in more complex code um where it's more difficult to ensure that you're actually using the correct type in the way that you're using variables it's a good question and it's rather a matter of opinion python was designed to be a little more versatile and flexible when it comes to some of these details partly for
uh writeability to make it easier and faster to write code partly for performance so that the program like python doesn't have to bother checking these kinds of details we can just get right into the code the reality though is that strong type checks do tend to be a good thing for the correctness of your code why because programs like mypi can find before your code is even run if there's already known to be an error and it tends to be good for defensive programming so the the situation essentially is that within the python ecosystem you
can annotate your types in this way you can use tools to use those type hints but to date python itself does not enforce or expect to enforce these conventions in larger code bases in professional code bases commercial code bases probably depending on the product project manager or depending on the engineering team they may very well want themselves to be using type-ins why if it just decreases the probability of bugs in fact let me propose now that i imagine a situation where instead of expecting that meow prints meow meow meow some number of times suppose that
i accidentally assumed that the meow function just returns meow some number of times we saw for instance when focusing on unit tests that it tends to be a good thing to have functions that return values be it an int or a string rather than just having some side effect like printing things out themselves so perhaps i'm still in that mindset and i've just assumed mistakenly for the moment that meow returns a value like meow or meow meow or meow meow meow a big string of some number of meows rather than just printing it itself as
it clearly does at the moment on line three and therefore suppose that i accidentally did something like this rather than just getting the number and passing it to meow suppose i did this suppose i declared a number of a new variable called meows the type of which i think should be stir and suppose again i assume accidentally that meow returns to me a string of those meows so that i myself can then print them later this would be a little more conducive arguably to testing my meow function why because i could expect that it's returning
meow or meow or meow meow meow separated by new lines returning a stir that i could then assert equals what i expect it to be in something like a unit test i'm not going to bother writing any unit test now but let's just suppose that's the mindset i'm now in and so on line seven i'm assuming that i want to assign the return value of meow to a new variable called meows which i've annotated with this type hint as being a stir just so we can see another variable this one's not an int but a
stir instead well let me go ahead and run this code now python of meows.pie enter typing in three and you'll see a curious bug meow meow meow none well why is that well it turns out at the moment my meow function only has a side effect it just prints out meow some number of times it doesn't explicitly return a value as it would if there were literally the return keyword there by default then when a function in python does not explicitly return a value its implicit return value is effect none and so what we're seeing
here is this on line eight because i'm assigning the return value of meow which is none to my meow's variable line three is what's still printing meow meow meow and line eight is what's now incorrectly printing none because i accidentally thought that meow returns a value but it doesn't so it's about return value is effectively none so i'm printing very weirdly the word none at the bottom so how could i go about catching this kind of mistake too like i might make this mistake but maybe with less frequency if i'm in the habit of annotating
my code with this new feature called type hints what you can do here is this let me clear my terminal window to get rid of that artifact and up here let me additionally specify with some funny looking syntax that my meow function actually by design returns none so you literally use this arrow notation in python when hinting what the return value of a function is you would do this after the parentheses a space a hyphen a greater than symbol like an arrow and then another space and then the type of the return value for now
it's indeed going to excuse me return none but now at least i can catch it like this if i now run not python but my pi on my code which would be a habit i'm now getting into if using type hints check that i'm using all of my types correctly before i even run my program we'll see that now my pi has found on line 7 that meow quote-unquote does not return a value and my pi knows that because i have proactively annotated my meow function as having none as its return value so now my
pi can detect that i should now realize oh wait a minute i'm being foolish here my meow clearly does not return a value i should not be treating it like it does on line seven let me go about actually fixing this now so how do i go about fixing this well let's practice what we preached in our focus on unit tests having a function like meow not have side effects like printing itself but let's have it return the actual string and i can actually do this kind of cleanly let me uh clear my error message
in my terminal window here let me get rid of the loop here let me say this time that okay fine meow is going to return a value an actual stir or string so i've changed none to stir and now i can implement this in any number of ways maybe even using a loop but recall that we have this syntax in python which will i think solve this problem for us if i want to return a string of n meows what i can actually do recall is this return quote-unquote meow backslash n times that number n
so it's kind of a clever one liner avoids the need for a for loop or something more involved than that to just say multiply meow backslash n against itself three times essential or end times in this case in general so that i get back a big string of zero meows one two three or many more mouse instead i think now my code on line six is actually correct now i've changed meow to behave the way i was pretending to assume it always worked so i'm storing in meows plural a variable that's a type stir because
now meow does have a return value of type stir itself per this type hint as well all right let me go ahead now and print meows but because each of my meows comes with a trailing new line the backslash n i'm going to proactively fix what would be a minor aesthetic bug and i'm just going to avoid outputting an extra new line at the end of those three so if i run python of meows dot pi now type in three there's my meow meow meow and now no mention of none questions now on type hints
and these annotations in my pie and using them to defensively write code that just decreases hopefully the probability of your own bugs the return is sad the initial returns have double quotes that have meow slash n why the program don't take it as a strange why does the program not take it as a as strange so so recall that early on in the class we looked at plus as a concatenation operator that allows you to join a string on the left and the right multiplication is also an overloaded operator for strings whereby if you have
a string on the left and an int on the right it will multiply the string so to speak by concatenating or joining that many meows all together so this is a feature of object oriented programming and and operator overloading as we saw it uh in the past other questions on type hints or my pi do we not typecast this data type uh the you know of this variable number no you still and let me correct the terminology it wouldn't be called type casting in this context uh because it's not like c or c plus plus
where there's an equivalence between these types you're technically converting on line five a stir to an int you do still have to do this because my pie for instance would yell at you if you were trying to assign a stir on the right to an int on the left you must still use the int function int itself is still a function it's not a type hint but the word int is being used in another way now in these type hints so this inf is still a function call as it always has been this syntax on
the left is another use of the keyword int but in the form of these type hints so you still have to do the conversion yourself all right let me propose that we transition to another feature of python that's worth knowing especially since it's one that you'll see in the wild when you see code or libraries that other folks have written namely something known as a doc string or document strings it turns out in the world of python there is a standardized way per another pep python enhancement proposal this one 257 that essentially standardizes how you
should document your functions among other aspects of your code and so for instance let me go back to my meows.pi file here and let me propose that we now start documenting this code too so that i know what the meow function does and in fact the standard way of doing this using doc string notation would be as follows to comment this function not above it as you might be in the habit of doing with code in general but actually inside of it but instead of commenting it like this with the usual hash comment sign like
meow end times it turns out that when you're formally docking when you're formally documenting a function like meow in this case you don't use regular inline comments so to speak you use this syntax instead you use triple quotation marks either double or single then you write out your comment meow and times and then you write the same again at the end so either three double quotes at the start and the end or three single quotes at the start and the end and python has built into it certain tools and certain assumptions that if it detects
that there is a comment using this docstring format triple quotes on the left and the right it will assume that that's indeed the documentation for that function and it turns out in the python ecosystem there's a lot of tools that you can then use to analyze your code automatically extract all of these document strings for you and even generate web pages or pdfs of documentation for your own functions so there's these conventions by which if you adhere to them you can start documenting your code as for other people by generating automatically the documentation from your
own code without writing something up from scratch manually now it turns out if your function does take arguments and perhaps does a bit more there are multiple conventions for how you can document for the human programmers that might be using your function whether it's you or a colleague or someone else on the internet to actually use these doc strings to standardize the information they're in so you might see this instead using the same triple quotes above and below now you might see your one sentence uh one sentence explanation of the function meows meow end times
uh sometimes depending on the style and use it might actually still be on the first line but with a blank line below it but i'll keep everything uniformly indented and this is a convention used by some popular python documentation tools as well you would say syntax like this param n colon and then a description of what n is number of times to meow then colon type n colon int which just indicates that the type of n is an integer then if this function could actually raise an exception you can document that too and actually it's
not really well it's arguably my mistake here if n comes in as an argument and is not in fact an int maybe it's a float or a string or something else the multiplication sign here is not going to work it's not going to multiply the string it's going to trigger what i know from experience to be a type error so i'm going to go ahead and proactively say in my own documentation that this function technically if you use it wrong could raise a type error even though i'm hinting up here with this annotation that you
should pass in an int again python doesn't enforce that so if you pass in a float this might in fact raise this function a type error and so that might happen if n is not an int and then lastly i might say for clarity's sake for other programmers this function returns a string of n meows one per line and the return type of that value our type is going to be stir now all of this syntax here as i've used it is not python per se this is a convention known as restructured text which is
a form of markdown like language that's used for documentation for websites for blogs and even more but it's one of the popular conventions within the world of python to document your own functions so this does not have anything to do fundamentally with type hints type hints are a feature of python what i'm doing here is just adhering to a third-party convention of putting in between a python docs string from the start to the end a certain standard format so that these third-party tools can analyze my code for me top to bottom left to right and
ideally generate documentation for me it can generate a pdf a web page or something else so that i or my colleagues don't need to not just only write code but also manually create documentation for our code we can keep everything together and use tools to generate the same met for us any questions now on these doc strings which again are a convention of documenting your own code often following some standard syntax yeah so when you say like you would document it and put it in a pdf is the purpose of doing this to kind of
like publish it and share your function so other users can use it absolutely in the past when we have installed some third-party libraries for instance cause a few weeks back recall that i showed you what functions it had but if you read the documentation you might actually see that uh it was documented for us by the author of that program now i don't believe they were using this particular syntax but it was definitely useful for you and me to be able to read some webpage or pdf telling us how to use the library rather than
wasting time reading through someone else's code and trying to infer what functions exist and how to use them it just tends to be much more developer friendly to have proper documentation for our own code or libraries as well other questions yeah um when with docstrings when you when it's used to generate like a pdf or whatever does it does it include uh any of the code so if you're referencing in the in your in your comment uh if you're referencing the code and the comment itself might not make sense without seeing the code does it
does do these inc included short answer you can do that not in the convention i'm using here but there's actually a clever way to write in your doc strings sample inputs to your functions and sample outputs for your functions and if you use a different tool that we've not discussed that tool will run your code using those sample inputs it will check that your outputs match your sample outputs and if not the program will yell at you saying you've got a bug somewhere so this is just another way where you can use doc strings to
not only document but even catch errors in your code this has been a lot and there's a bit more to go why don't we go ahead here and take a five minute break and when we resume we'll take a look at yet another feature of python yet another library to write code faster all right suppose we want to modify this meows program to actually take its input not from the input function in the blinking prompt but from the command line recall in our discussion of libraries that you could use something like sys.rgv to get at
command line arguments that a human has provided when you're running your program so why don't we whip up a version of meow that uses command line arguments instead of again input so i'm going to go ahead and delete what we've done here thus far and let me propose that we import sys as we've done in the past and let's do this how about if the user does not type any command line arguments then my program will just meow once just so that it does something visually interesting otherwise let's also give the user an option to
specify how many times i want the cat to meow so let's start simple let's first of all go ahead and do this if the length of cis.rgv equals equals one that is the user only typed the name of the program and nothing else after the uh in their command then let's go ahead and just print out one meow like this uh else for now let's go ahead and print out something like this else go ahead and print out let's say usage for the program which will be usage of meows dot pi just so that the
user knows that the program itself is called meows.pi all right now let me go down to my terminal window and start to type python of meows.pi and at this point notice that the length of sys.rgv should indeed be one why well python the name doesn't end up in sys.rv at all ever but meows.pi the name of the file does and it's going to go in sys.rgv0 but that's only one element so the length of this thing is one there's nothing more to the right so when i hit enter now we should see indeed one meow
if i don't cooperate suppose i do something like meows 3 enter then i'm going to see a reminder that this is how you use the program and this is a common convention to literally print out the word usage a colon then the name of the program and maybe some explanation of how to use it so i'm keeping it very simple but let's be a little fancier what if i really wanted the user to type in maybe not three but something more sophisticated and in fact when controlling programs from the command line it's very common to
provide what are often called switches or flags whereby you pass in something like dash n which semantically means this number of times then often a space and then something like the number three this still allows me to do other things at the command line if i want but the fact that i've standardized on how i'm providing command line arguments to this program with dash n3 is just a more reliable way now of my program knowing what is the three mean it's a little less obvious if i just do meows.pi space three well what does the
three mean at least with syntax like dash n3 especially if you've read the documentation for this program ultimately oh dash n means number of times got it it's a way of passing in two additional arguments but that have some relationship between them so how do i modify my program to understand dash n three well if i'm using cis like this i could do this l if the length of cis dot argv equals this time three because notice there's one two three things at my prompt so sys.org be zero one and two three things total separated
by spaces if it equals three and let's be safe and sys.rgv bracket 1 equals equals dash n then let's go ahead and do this let's go ahead and convert sys.rgv of 2 to an integer and assign it to a variable for instance called n and then let's go ahead and do this for underscore in the range of n let's go ahead and print out some of these meows now there's still an opportunity maybe to consolidate my print lines with meow but for now i'm going to keep these ideas separate so i'm going to handle the
default case with no arguments up here as before and now more interestingly i'm going to do this to be clear i'm going to check if the user gave me three command line arguments the name of the program dash n and a number if indeed the second thing they gave me in sys.rgv of 1 equals equals dash n then i'm going to assume that the next thing sys.rgv of 2 is going to be an integer and i'll convert it to such and store it in this variable n and now just using a loop i'm going to
print out meow that many times all right so it's kind of a combination of our earlier focus on loops our earlier focus on command line arguments just creating a program that allow me to claim is representative of how a lot of command line programs work even though we've typically not used many like this but it's very common to configure a program one you're about to run into the command line with something like these command line arguments like dash n or dash something else now i'm gonna go ahead and hit enter and i think i should
see indeed three meows by contrast if i do two at the end i should see two meows if i do one i should see one meow and frankly if i just omit this all together i should see one meow as well because that was my default case earlier and now let me allow us to uh assume that this program eventually gets more complicated right let's imagine a world where i don't want to support just dash n maybe i want to support dash a and dash b and dash c and dash d and a whole lot
of others or heck at that point i should maybe give them words so maybe it's a dash dash number it's indeed a convention in computing typically to use single dashes with a single letter like n but use double dashes if you're actually using a whole word like number so the command line argument might be dash n or maybe it's dash dash number but you can imagine just how complicated the code gets if now you want to support dash n dash a dash b dash c and so forth you're going to have to be checking all
over the place and what if they come in a different order you're gonna have to check is dash n first or is it second or is it third or is its fourth i mean this just becomes very painful very quickly just to do something relatively simple like allow the user to pass command line arguments into your program well this is why as always there exist libraries and another library that comes with python that's probably worth knowing something about is this one here called argparse in fact with a lot of the tools i myself or cs50s
team writes in python we very frequently use arg parse whenever they are more complicated than a lot of our class demos and a little more similar to this one where we want to allow the user to pass in configuration options at the command line and by supporting things like dash n or dash a or b or dash c argparse is a library that per its documentation just handles all of this parsing so to speak this analysis of command line arguments for you automatically so you can focus on writing the interesting parts of your program not
the command line arguments part so how might we use this well let me go back to vs code here let me clear my terminal window and let me propose that i rewrite this using not sys but actually using arg parse and i'm going to start a little simple and then build back up so let me throw all of this away for now and instead import arg parse arg parse stands for argument parser to parse something means to read it kind of pick it apart to analyze it so this is indeed going to do just that
for me now let me go ahead and do this and for this library it's helpful to know a little object oriented programming like we all now do i'm going to create a variable called parser i could call it anything i want i'm going to set it equal to the return value of argparse dot argument parser with a capital a and a capital p a constructor for a class called argument parser that comes with python itself within this library here now i'm going to configure this argument parser to know about the specific command line arguments that
i myself want to support in my program so i'm going to do this parser.add underscore argument so that's apparently a method in the parser object i'm going to add an argument of dash n easy enough now i'm going to go ahead and actually parse the command line arguments i'm going to do args or i could call the variable anything i want parser dot parse args and by default parse args is going to automatically look at sys.org v for me i don't need to import cis myself i can leave the argument parser its code to import
sys look at sys.org v and figure out where dash n or anything else actually is and what's nice now because this line of code here results in the parser having parsed all of the command line arguments i now have this object in this variable called args inside of which are all of the values of those command line arguments no matter what order they appeared in not such a big deal when i've only got one because it's only going to go in one place at the end but if i've got dash n dash a dash b
c you could imagine them being in all different orders they definitely don't have to be alphabetical the user should be able to type them in any order they want that's better for usability arg parser is going to figure all of that out for me and all i have to do now is this if i want to iterate over that many numbers of arguments and that many mouse rather i can do this for underscore in the range of the int conversion of args.n so dot is the syntax we kept using to access things like properties inside
of an object and that's what args is it's the object returned by the parsers function for me i'm going to go ahead now and print out quote unquote meow this many times so it's not super simple like these are three new lines of code i need to write and rather understand but it's already a little simpler and more compact than my if and my l if and my ors and my ands and all of that boolean logic it's handling a lot of this for me so if i didn't make any mistakes let me run python
now of meows dot pi enter and i did make a mistake here i did make a mistake what's what's wrong here now what's wrong well i definitely didn't run it the way i intend so dash n 3 enter so it does work but if i don't cooperate this actually seems to be a worse version if i don't pass in dash n and a number it just airs with a type error it must be a string none is what came back so there's clearly an error here but the library is more flexible i can actually provide
some documentation on how to use this thing so how do i know how to use this well typically it's conventional in python and in a lot of programming environments to run a program with a special argument dash h or dash dash help and almost always i will claim you'll then see some kind of usage information indeed that's what i'm looking at now i just ran python of meows dot pi space dash h i'll do it again let me clear my screen and this time do dash dash help in english enter and i see the same
thing it's not very useful at the moment it just shows me what the usage is up here and this is kind of interesting this is a standard syntax and commune computing and we've kind of seen it in python's documentation before this just means that the program's name is of course meows.pi square brackets as almost always in documentation means it's optional so i don't have to type dash h but i can i don't have to type dash n and another value but i can and then down here is some explanation of these options and more verbally
showing me that i can also do dash dash help and not just dash h but this is so generic this has nothing to do with my program this is not going to help my users when i actually release this software for the world so let me go ahead and improve it let me add a description to my argument parser that the humans will see meow like a cat quote unquote is going to be the value of this named parameter called description and let me also add a help parameter to my dash n argument that just
explains what dash n means number of times to meow quote unquote i'm not going to change anything else but i am going to go back to my terminal window and run python of meow i'm going to run python of meows dot pi h or equivalently dash dash help and now notice that this is a little more user friendly if i scroll up we still see the same usage but there's a quick sentence in english of explanation that this program meows like a cat and if i look at the options now oh that's what n means
it's the number of times to meow and this capital end a middle variable if you will is just indicating to me that i need to type a number by convention after the lowercase dash n so it would be nice though all that said if my program still didn't just break when i run it without any command line arguments right ideally my program would handle this just like my manual version did when i used sys.rgv myself so we just need to add a little more functionality to this library and if i read the documentation i'll see
that add argument takes yet another named argument if you want you can specify a default value for dash n for instance one and i'll do that there and you can further specify that it's got to be an int and what this will additionally allow me to do is if i tell our parser to make sure that the value of dash n is an int i don't need to do the conversion manually i can just trust down on line 7 that when i access the property called n inside of my args object it's going to be
automatically an int for me and again this is the value of a library and let it do all of the work for you so you can get back to focusing on the interesting project at hand whatever problem it is you're trying to solve like in this case granted not that interesting but meowing like a cat let me go ahead now and run python of meows.pi and hit enter this time no arguments and now it meows why because i specified that if i don't as a user specify dash n it's going to have a default value
of 1 apparently and i don't have to convert that value from a stir to an int because i told argparcer please just make this an int for me any questions now on arg parse or really this principle of just outsourcing the commodity stuff the stuff that everyone's program eventually needs to do so that you can focus on the juicy part yourself what does that n contain what does args.n contain it contains the integer that the human typed after a space after dash n a good question other questions yeah did uh uh the the when you
specify the type um for the argument uh what happens if does that basically handle the exception if if the user inputs a string in this case a really good question suppose that the human does not type a number and therefore not an n well let's see what happens so python of meows dot pi dash n dog where dog is obviously not a number enter and voila we see an automatically generated error message a little cryptic admittedly but i'm seeing a reminder of what the usage is and a minor explanation of what is invalid about this
and again this is what allows you this is what allows me to like focus on writing the actual code we care about and just letting the library automate some of this stuff for us all right well allow me to propose now that we take a look at one other feature of python that we've seen before but it turns out we can use it even more powerfully as our programs become more sophisticated and the problems we're trying to solve themselves become more involved let me go ahead and return to vs code closing out meows.pi and creating
a new file for instance called unpack.pi so code of unpack.pi and let me just remind us like what we mean by unpacking because this is actually a feature of python that we've seen before for instance suppose that i write a program that prompts the user for their name like david malen wouldn't it be nice if we could sort of split the user's name into two separate variables and when we've done this in the past we've done it in a few different ways but one of them involved unpacking a single value uh that comes back from
that like a list or some other data structure and putting it immediately into two variables so let's do this here let me go ahead and call the input function asking someone what's your name question mark then let me go ahead and just split a little naively on a single space so i'm assuming that the only users at the moment are people like me david space malin no middle names no multiple names it's just one and two which itself could be buggy for other users but for now i'm keeping it simple just to remind us that
i can now unpack that return value with something like first underscore last equals the return value of input and now i can go ahead and do something like this like printing out with an f string hello comma and then curly braces first if i just want to greet myself or any other user as hello david without the last name and frankly if i'm not using the last name recall that a python convention is just to name it underscore to make clear that you know you're not using that value but it does need to be there
because you're unpacking two values at once so if i run this it won't be all that unfamiliar i'm just going to run now python of unpacked up high i'll type in david malen which has a single space and there we have it hello comma david well it turns out that there's other ways to unpack values and there's other features that python offers especially when it comes to defining and using functions and this is slightly more intermediate functionality if you will that's useful because you can start to write even more elegant and powerful code once you
get comfortable with syntax like this so let me go ahead and propose that we not just play with hello hello names anymore but instead do something maybe involving some coinage again so maybe not dollars and cents but maybe again as in the past some galleons and sickles and canuts with among which there's a mathematical relationship as to how many of those in the wizarding world equal each other and let me go ahead and do this let me define a simple function called total that just tells me the total value of someone's vault in gringotts the
wizarding bank based on how many galleons sickles and canuts that they have which again are currencies from the wizarding world as opposed to our actual human world so this total function might take a variable like galleons and sickles and canuts like this and then it's going to return the formula which i admittedly had to look up myself and it turns out that the formula for converting galleons and sickles to canuts would be this galleons times 17 plus sickles then times all of that by 29 and then add in the individual canuts not sure in what
detail this came up in the books or movies but here we have it the official formula all right now let's go ahead and do this let me go ahead and call the total function with just some sample inputs suppose that someone like harry has a hundred galleons 50 sickles and 25 canuts let me go ahead and print that out on the screen alright well if total returns an integer which i think this arithmetic expression will do let me go ahead and store rather pass the return value of total to print and then just for clarity
let me write canuts at the end so i know that the unit of measure here is indeed canuts in total all right now let me go ahead in my terminal window and run python of unpack.pi and hit enter and it turns out mathematically that if i got my math correct a hundred galleons plus 50 sickles plus uh 25 cannots equals in total 50 775 canuts just avoiding having to use our own human currency here but i'm not doing anything along the lines of unpacking at least just yet let me propose now that i do this
just for the sake of discussion let me propose that i leave the total function as is but let me go ahead and just store all of my coins in a list so coins in order from left to right 100 50 25. it just because for whatever purpose is in this story i have all of my coinage in a list in this order kind of a purse or wallet of sorts well how can i pass this in well i'm not going to hard code the same values twice just for the sake of discussion how could i
pass in the individual elements of a list to my total function well of course i could treat this list as i always do using numeric indices by doing coins bracket zero coins bracket one coins bracket two so this is old school stuff with lists if i've got a list called coins and there's three elements the indices or indexes of those elements are 0 1 and 2 respectively from left to right so all i'm doing here now is passing in the first element from that list as galleons the second element of that list as sickles and
the third element of this list as my canuts and that lines up with of course the signature of this function which as total expects that i've passed in those three things in that order left to right all right let me go ahead and run just to make sure i haven't broken anything unpack.pi and hit enter and the math still checks out but this is getting a little verbose a little verbose and wouldn't it be nice if i could just pass the list of coins to this total function wouldn't it be nice if i could just
say something like this coins but let me pause and ask the group why would this not actually work as is it technically is passing in all three but why would i get some kind of error when i run this eric uh because you are passing a list to galleons yeah i'm passing a list to galleons and nothing for sickles and canuts and notice those don't have default values there's no equal signs on that first line of code which means python's not going to know what value should be assumed there so it just seems like it's
not going to work plus it's the wrong type as eric notes it's a list and it's not an integer as it was before so let's actually run this incorrect version python of unpack.pi enter type error and that is probably what you might expect like i'm messing up with the types here and i am required to pass in two positional arguments sickles and canuts that were not even passed so i've definitely erred here but it certainly seems unfortunate if the only solution to this is to do what i previously did which is index into the first
element index into the second element index into the third like you can imagine with bigger fancier functions that take even more arguments this is going to get very verbose and honestly very vulnerable potentially to just mistakes typos on my part but here too is where you can do what's known again as unpacking a value in python right now a list is kind of packed with multiple values my current list has these three values 100 50 and 25 respectively but they're all packed up in this one list wouldn't it be nice if i could unpack that
list just like i previously unpacked the return value of the stir classes split function into multiple things too and indeed i can do just that python actually allows me to pass in not coins but star coins so if you use a single asterisk at the beginning of your variable that will unpack it and it will take one sequence in this case coins of size three and explode it if you will unpack it into three individual arguments no commas are needed python just handles this for you but the effect of passing in star coins is to
pass in the individual members of that list which in this case are going to be 100 50 and 25 respectively which is perfect because now it's going to line up with galleons sickles canuts respectively so now when i run python of unpacked.i we're back in business and the math checks out but i've kind of cleaned up my code by just introducing this new symbol which we've used of course in other contexts for multiplication and the like but now it's also used for unpacking in this way questions on what we've just done it's a single operator
but it's already quite powerful because it allows us to take a data structure and unpack it and pass it in individually does that work for um tuples sets dicks dictionaries as well tuples yes sets i don't know wrong shin i don't know if order is preserved no oh is that no it does not or you know you're checking orders not preserved so it wouldn't work with set does not work with set does not work with set so i'm verbally googling here just to save us some keystrokes so it would work for enumerations that where order
is indeed preserved and we'll see another example in a moment where it actually can be used in a different way for dictionaries which nowadays do preserve order other questions on unpacking in this way yes hi hello how can you use unpacking to get the value uh for example 10 plus 50 plus 25 uh instead of a for loop and then result plus short answer no if you want the individual values you should be just indexing in this case into those specific locations this is returning multiple values the equivalent of a comma separated list so you
would use the earlier approach if you cared about the individual locations how about one other question on unpacking what if uh we have declared we we declare some default values and if you use these asterisk points will it work right or will it skip it good question if i heard you right what if for instance the list has four values like this here and you're still unpacking it when it's only three that's expected well let's try it python of unpack.pi enter another type error this time it takes three positional arguments but four were given so
the onus is on us as the programmer not to do that in this case so potentially fragile but avoidable if i'm controlling the contents of this list in fact let me propose now that we take a look at another variant of this whereby we use not just positional arguments whereby we trust that the first is galleons the second is sickles the third is knuts suppose that we actually passed in the names as we're allowed to do in python and then technically we could pass them in in any order and python would figure it out using
named parameters instead well how might i do this well it's going to be a bit of a regression at first so let me get rid of this list here let me change this now to just manually pass in the values i care about galleons i want to still equal 100 sickles i want to equal 50. and canuts i want to equal 25. so this is old-school parameter pressing it's no longer positional i'm explicitly specifying the names of these arguments but that's just going to work because that's exactly what the names of these parameters are in
my total function as before let's make sure i nonetheless did not break anything let's run python of of unpack.pi enter and there we have it still 50 775 canuts well once you start giving things names and values names and values that probably should bring to mind one of our most versatile data structures in python and even other languages that of a dictionary remember that a dictionary is just a collection of key value pairs names and their respective values so this kind of opens up an opportunity what if i did this what if i actually had
for some reason in my program on a variable as before called coins but instead of making it a list of three values like before what if it's a proper dictionary so what if it's galleons quote unquote colon 100 for 100 of those sickles quote unquote and 50 of those and canuts quote unquote 25 of those each of those separated by colons and let me fix my square brackets to this time be curly braces which recall is the symbol we use for dictionaries or dict objects in python so now i have a dictionary called coins not
a list it's a collection of keys and values three keys galleons sickles canuts and three values 100 50 25 respectively if i were to now pass these individual values into my total function i could do it as always with my dictionary so i'm doing it old school now coins is the name of my dictionary i index into it not with numbers like with lists but with words so galleons strings like this coins quote unquote sickles in square brackets there and then lastly coins square brackets quote unquote canuts so it's getting it's verbose again like this
is not maybe the best road to go down but we'll backpedal in a moment this is just how if you happen to have all of your coins stored in a dictionary you could pass the galleon sickles and canuts into your function respectively let's make sure i didn't break anything let's rerun python of unpack.pi and we're still good now how could we get to a situation like this well as always imagine this program is a little longer than this one here and somehow you're using a dictionary maybe just to keep track of someone's purse or wallet
like how many coins of each type that they have and as such it's perfectly reasonable to use a dictionary but then you want to print out the total and darn it if that total function does not expect a dictionary so you cannot just do something nice and simple like pass in coins for reasons we saw earlier that would be a type error total expects three arguments three integers you can't just pass in a dictionary but if that's the data structure you're using to store the person's purse or wallet well it's kind of unfortunate that we
have this clash between these data types well here's what we can do we can't pass in coins because watch if i try doing that and run python of unpack.pi we're getting another type error missing two required positional arguments sickles and cannuts i have to pass in three things but wonderfully python allows you to unpack dictionaries as well for a dictionary you don't use a single asterisk you use two and what this syntax has the effect of doing is passing in three values with names it has the effect of passing in galleons equals 100 comma sickles
equals 50 comma canuts equals 25 and so it has the similar effect to the list unpacking but that just passed in the values 100 50 25 separated by commas in effect when unpacking a dictionary it passes in the keys and the values separated conceptually with equal signs just like our function expects so if i now run python of unpacked up pi again we're still good but we've tightened our code up again and now i'm giving myself yet another option i can either store a wizard's purse or or wallets in their uh in a list as
we did earlier or i can store it a little more versus with even more specificity using a dictionary instead and so to be clear let me rewind star star coins is the same thing if i rewind a little bit to our first example of named arguments is equivalent to what i've highlighted here when you unpack a dictionary it passes in all of the keys and all of the values much like this syntax here but let me tighten it up and go to where we left off questions now on unpacking can we have a in this
dictionary can we have instead of having a constant name value pair can we have a variable number of you know name value pairs short answer yes you can have more than three key value pairs as i have here but it's not going to work unpacking it if the total function is expecting only three so if i were to add something here like let me introduce pennies to the wizarding world and suppose i have one penny for instance and now i run this same code python of unpacked.i we're back to a type error again whereby i
got an unexpected keyword argument pennies because that is not expected by the total function we will see in just a moment wonderfully a solution though to that but for now it does not work other questions on unpacking with dictionaries or lists in list english values we gave the same number of arguments and we declared a default value in the function now if you use this asterisk will it overwrite that value or will it skip it skip the default value a good question if you if we did have default values up here for instance equals zero
equals zero equals zero the upside of that recall from our discussion of arguments to functions a while back is that now you don't have to pass in all of those values they will default to those zeros therefore you could pass in fewer than three values either using a list or a dictionary that's unpacked in this scenario i deliberately did not do that because i wanted us to encounter this specific error in this case but you could absolutely go back and add those defaults so it turns out that this single asterisk or this double asterisk is
not only used in the context of unpacking that same syntax is actually used as a visual indicator in python when a function itself might very well take a variable number of arguments that is to say a function can be variatic which means that it doesn't necessarily have to take say three arguments specifically even if they do or don't have default values it can take maybe zero or one or two or three and it turns out the syntax for implementing the same idea is quite similar in spirit in fact let me go back to vs code
here and let me propose that we start over with this code and get rid of our notion of galleons and sickles and canuts and do something just a little more generic just so that we've seen the syntax for this suppose that i define a function as follows define a function let's call it f and that function is not going to take a specific number of arguments but a variable one and so i'm gonna go ahead and use this syntax here star args which indicates that this function is indeed variatic it takes some variable number of
positional arguments positional in the sense that they go typically from left to right but i don't know how many just yet i want to support suppose that i additionally want to support some number of keyword arguments that is named parameters that can be called optionally and individually by their own name well the convention syntactically here would be to use two stars and then kw args i could call args or kw args anything else that i want but a convention you'll frequently see in python's own documentation is that when you have placeholders like this for some
number of arguments and some number of keyword arguments the world tends to use args and key kw args well inside of this function let's do something super simple just for now let me go ahead and print out literally quote unquote positional just to indicate to myself while wrapping my mind around what's going on here what the positional arguments are and let me quite simply print out those args this is not something you would typically do you don't typically just take in these arguments and print them no matter how many there are i'm just doing this
diagnostically for now to show you how the syntax works now let me go ahead at the bottom of my file and i won't bother with a main function this time so we can focus only on this function f let me go ahead and just call f with three arguments i'll use the same arguments as before but i didn't bother giving them names just yet like galleons and sickles and canuts and the like so what do i have a program that no matter what calls this function f but it first defines f at the top of
the file is taking some number of positional arguments some number of named arguments and for the moment i'm just printing out the positional ones let me go ahead and in my terminal window run python of unpack.pi and hit enter and you'll see that the positional arguments passed in are apparently this a sequence 100 50 25. but notice this if i clear my terminal window there and pass in something else like 5 a fourth argument previously if i try to change the number of arguments i'm passing in to my total function which was only defined as
taking 3 i would have gotten a type error some visual indication that no you can't pass in more or fewer arguments than is actually in the function's definition but now watch if i run python of unpack.pi this time passing in 100 50 25 and 5 a fourth argument all four of those went through just fine i can get rid of all of those but one for instance now re-run my program after clearing my screen and now i'll see just one argument here and even though there's a comma and nothing after it this is actually the
syntax when seeing a tuple in effect whereby the comma just indicates this is indeed a list but there's only one element therein well let's get a little more curious too let me go ahead and rewind here to where we started with just those three values and this time let me go ahead and print out my named argument so to speak which isn't args but kw args again the positional args in this syntax come first the named arguments kw arcs come second that's what python prescribes so now let me go ahead and not pass in just
these numbers let me go ahead and pass in actually named arguments so let me do something now more specifically like galleons equals 100 and sickles equals 50. and canuts equals 25. i'm not going to bother doing any math with total i just want to poke around right now at this functionality of having a variable number of arguments and what's neat now is if i run python of unpacked apply and hit enter no problem what kw args is is automatically a dictionary that contains all of the named arguments that were passed to my function which is
to say when designing your own functions if you want to support more than one argument maybe more than two or three or four maybe a variable number of arguments indeed you can support both a variable number of positional arguments that are just value comma value comma value or any number of named arguments where you actually put the name of the parameter equals the value and then maybe a comma and some more of the same so now it turns out we have seen this before in some of the functions we've used to date we didn't necessarily
see it called args or necessarily see it called kw args but we have seen at least one example of this in the wild recall our old friend print which we've been using now for weeks and when we first looked at the documentation for print way back when it looked a little something like this the first argument to print was objects and i waved my hand at the time at the asterisk that was at the start of that variable name but then we had sep for separator the default value of which was a space we had
n the default value of which was a new line and then some other named arguments that we waved our hands at then and i'll again do now but what you can now perhaps infer from our emphasis on these asterisks today the single stars or the double stars is that you know what this is the convention in python's documentation to indicate that print takes a variable number of arguments so if we were to look at the actual implementation of the print function implemented by python's own authors it might very well look something like this def print
and then the first argument would be star objects thereby indicating that print takes a variable number of arguments the next one of which might be sep equals quote unquote either using double quotes or as in the documentation single quotes two the next one of which might be n the default value of which is a new line and then some of those other named arguments that we've not looked at as well and then maybe inside of the print function implemented by the authors of python maybe there's a for loop like for object in objects that allows
them to iterate over each of those variable number of objects and print each of them and this is why in programs past you and i have been able to do just print open parenthesis close parenthesis with nothing inside or you and i have been able to print out something like hello world a single string inside of those parentheses or you and i have been able to do a single string hello and then another string quote-unquote world thereby passing in two arguments or even more so we've long had this ability to use variatic our functions whereby
you can pass in a variable number of arguments what you now have via this args and kw arc syntax but again they do not need to be called that is the ability using that star or two stars to implement those kinds of functions yourself my own f function a moment ago did not do anything all that interesting but it hints at how you could if in the future you have a use case for taking zero or one or more of either type of argument any questions now on these types of arguments what will happen if
you print quarks and the argument is like a list ah so what would happen if you print the argument like it's a list so i think we saw that if i roll back in my history here to when i had that f function which i called f just to be very generic just so we could play around with the syntax this is what i had here so this is a um i passed in 100 comma 50 comma 25 that gets automatically stored in args and when i run it you can actually see that sequence of
values by running python of unpacked up high there is that sequence all in the form of one single variable i'm printing it just for diagnostic purposes this is not really a useful or pretty program but it hints at how we can access that whole sequence of values other questions on this approach here can we pass kw args from one function to another function absolutely you can pass either of those to another function which you might want to do if you want to wrap another function provide some additional functionality but still pass in all of the
ex supported arguments to the underlying function as well all right how about this next it turns out that a few other tools we can add to your tool kit relate to the types of programming models that python supports we started out quite some time ago focusing really on procedural programming in python whereby we wrote code top to bottom left to right defining some functions or if you will procedures along the way defining variables and having side effects and assigning values as needed but we then eventually introduced or really revealed that python is also very much
object oriented and a lot of those variables a lot of those types that were using all that time were in fact objects objects that were came from certain classes and those classes were templates of sorts blueprints via which you could encapsulate both data and functionality therein well we also saw along the way some hints of a third paradigm of programming that python also to some extent it supports which is known as functional programming whereby functions are ever more powerful in that they tend not to have side effects no printing or changing of state globally but
rather they're completely self-contained and might take as inputs and return values and that's generally a paradigm we saw when we started sorting things particularly with functions like our sort function or lambda function when we passed in the function we wanted to use to sort a list way back when well it turns out python has other functionality that is reminiscent of functional programming and indeed is a powerful way to solve problems a little more differently still let me propose this let me propose that i whip up a new program here in vs code by closing our
unpacked up hi and this time creating another program called yell suppose the goal at hand is to implement some program that allows the user to pass in input and then it yells the response by forcing everything to uppercase my apologies to those with with headphones there i'll i'll modulate so let me go ahead and run code of yell.pi and within yell.hi let's go ahead and implement a program that really does just that let's go ahead and define a main function up here and let's assume for the moment that this yell function already exists and yell
something like this is cs50 properly capitalized not in all caps now let's go ahead and implement this yell function with def yell it's going to take for now a single uh word or phrase and let's go ahead and i'll call it phrase here and i'm gonna go ahead and just print out the phrase dot upper so phrase.upper is gonna force the whole thing to uppercase and as usual down here if the name of this file equals equals quote unquote uh main then let's go ahead as always and call main so let's just run this but
for the most part it should be fairly straightforward when i run python of yell.pi this is cs50 is yelled on the screen all right that's nice but it's not great that yell only expects a single expects a single phrase wouldn't it be nice like print if i could pass in one phrase or two or three or really multiple words more generally but as individual words themselves so let me retool this a little bit and change yell to take in not a phrase but how about something like a list of words so that ultimately i can
call yell like this quote unquote this inside of a list quote unquote this inside of a list and quote unquote cs50 inside of a list i'm not going to bother with type hints or annotations for now but i'll just assume that yell has been defined now is taking a list of words as defined here but now i want to force them all to lowercase so i don't quite want to do something as simple as this like for word in words i could for instance print that given word and maybe end the line with nothing right
now but i think if i do this python of yell.pi no that's that's not right i haven't forced anything to uppercase so let's fix this well let's go ahead and do the following let me go ahead and accumulate the uppercase words as follows let me create a variable called uppercased and initialize it to an empty list using square brackets or our more verbose list syntax and now let me go ahead and iterate over each of those words in words and for each of them let's go into our upper cased list append to it the current
words uppercase version so this is a way of creating a new list called uppercased that is just appending appending appending to that list each of the current words in the loop but uppercased instead and now just let me go ahead and print out the uppercase list this isn't quite right let's see what happens here python of yell.pi okay it's not quite right because i don't think i want those quotes or those square brackets what am i seeing i'm actually printing a list but but but here's where some of our unpacking syntax now can be useful
i don't have to change my approach to this problem i can just unpack uppercase by adding a single star and now let me go ahead and rerun python of yell.pi and now it's actually just english there's no remnants of python syntax like the quotes and the commas and the square brackets i've now unpacked this is cs50 as three separate arguments to print so already now this unpacking technique would seem to be useful well it's a little unfortunate that i now need to call yell though with a list of values in this way this is just
not the norm or at least it's not nearly as user-friendly as something like the print function where i can pass in zero or one or two or three or any number of arguments why are you making me for your yell function pass in only a list well we can do better let's adopt some of the new conventions we've learned and let's go ahead and get rid of the list by removing the square brackets and let's just pass yell three arguments now i don't want to do something like change the definition of words to take in
like word one word to like that's not going to scale and it's not going to handle different number of words but we have a technique now we can say star args which will allow the yell function to accept any number of arguments and just for specificity let's not call it generically arcs let's name it something a little more self-explanatory like star words this just means i have a variable number of words being passed in now i think i've made a marginal improvement let me run this again python of yell.pi this is cs50 is in all
caps but it's just a little better right because now i can treat yell just like i've long treated print pass in as many things as you want and print will deal with it now my yell function is just as powerful it would seem and better still it also forces everything to uppercase well it turns out python comes with this function called map whose purpose in life is to allow you to map that is apply some function to every element of some sequence like a list so for instance if we want to force to uppercase each
of the words this is cs50 in the list of words that's been passed in well we essentially want to map the uppercase function to each of those values so using map in python can i do just that let me go back here to vs code and let me propose now that i re-implement this as follows i get rid of all three of these lines here getting rid of that loop in particular let me still declare a variable called uppercase but let me set it equal to the return value of this new function called map map
takes two arguments here in this case the name of a function that i want to map onto a sequence of values well what function do i want to apply to every word that's been passed in well it turns out thanks to my knowledge now of object oriented programming i know that in the stir class there is a function called upper we've usually called it by using the name of a string variable dot upper open paren close paren but if you read the documentation for the stir class you'll see that the function is described indeed as
stir dot upper i'm not using parentheses open and close at the end of stir.upper because i don't want to call it now i want to pass this function to the map function so that map can somehow add those parentheses so to speak and call it on every one of these words and this is what map does quite powerfully and as an instance indeed of functional programming whereby i'm passing to this map function another function not calling it i'm just passing it in by a reference of sorts and what map is going to do for me
is iterate over each of those words call stir.upper on each of those words and return to me a brand new list containing all of those results together in one list it completely obviates the need for me to do this more manually using that list i'm still gonna print the whole thing using star uppercase so that if i get back a three a list of three uppercase words i'm gonna unpack them and print them all out so let's run this again python of yell.pi enter and voila it's still working but the code now is even more
tight uh even tighter than before so it turns out there's another way we can solve this problem in a way that's even more pythonic or at least quite common and that's using a feature known as a list comprehension and it's a big phrase if you will but it refers to the ability in python for you to very easily construct a list on the fly without using a loop without calling a pen and a pen but to do everything in one dare say elegant one-liner so how can i go about using this notion of a of
a list comprehension well let me go ahead and do this in yell.pie in vs code here let me go ahead and change my approach as follows instead of using map which is perfectly fine and correct in this way let me just show you this other way as well a list comprehension is the opportunity to create a list like this using square brackets like this but inside of those square brackets to write a python expression that in effect is going to dynamically generate a brand new list for you using some logic you've written and the approach
i might take here is this if i want to store in this list the uppercase version of every word in that words list i can do this word dot upper for word in words now this is a mouthful but i dare say python programmers love this capability of being able to define on the fly a list inside of which is any number of values that you would ordinarily at least as we've done it construct with a loop and again calling append and append and append but that usually takes two three four or more lines this
list comprehension that i've highlighted here is now an alternative way to create the exact same thing a list inside of which are a whole bunch of uppercased words which words for each word in the words list that was passed into yell is what ends up in this list questions on this syntax here it definitely takes a little bit of getting used to because you've got like this value on the left this function call here you've got this loop inside of the square brackets but if you become accustomed to reading the code in this way from
left to right this means give me the uppercase version of the word for each word in my words list questions here on list comprehensions can you do or also like if else or uh combine if l if else indeed you can and let me come back to that where we'll see an opportunity to do things conditionally but for now i'm just uppercasing every word in the list a good question other questions yeah um is is this is this functional programming or uh i mean this particular thing where you're saying words dot upper for word in
words not necessarily this is more of a feature of python i would say yeah map was uh one uh very specific incarnation of there of our use of lambda and passing it in as a key attribute to the sort function sorted function a while back was an example and we're about to see one other so we can even use these list comprehensions to filter values in or out of our resulting list so in fact in vs code here let me close yell.pi and close my terminal window and let me create a new program here whose
purpose in life maybe is to take a same list of students as before with a shorter version thereof and just filter out all of the students in gryffindor so let me go ahead and create a file called gryffindors.pie i'm going to go ahead and copy paste from before really my list of students at least hermione harry ron and draco from the start here just so that i can focus on one student who happens not to be from slytherin and what i'm going to do here now if i want to filter out only the gryffindor students
let me go ahead and do this let me create another variable called gryffindors which is going to equal the following list and this is going to be a bit of a longer line so i'm going to proactively move my square brackets onto two separate lines and i'm going to create now a list comprehension i want to do this i want this new list called gryffindors to contain every student's name for each student in the students list but but but if the student's house equals equals quote-unquote gryffindor so this is nearly identical in spirit to what
i just did earlier to create a list comprehension out of each of the words passed to my yell function but here i'm doing so conditionally and so i'm borrowing inspiration from our focus on loops borrowing some info inspiration from our focus on uh conditionals combining that into this same square bracket notation so that what gryffindor's ultimately is is zero or more students names and the names that are included are the result of iterating over each of those students and only including in the final result the students whose house happens to be gryffindor so when i
go ahead and run this with python of gryffindor's dot pie and hit enter you'll see huh nothing actually happened here well that's because i didn't finish the program let me go ahead and actually finish the program with this how about for each gryffindor in gryffindor's plural and better yet so that it's sensible that i did all of this work in advance let me go ahead and sort all of those names with our familiar sorted function let's go ahead now and print out each of these gryffindors so now notice if familiar with the books in the
movies you'll know that only three of these four students are actually in gryffindor and if i run python of gryffindor dot pi there we see harry hermione and ron but now in sorted order as well so that's just one way we can solve this same problem using not just a list comprehension but a list comprehension that has this conditional therein but there's yet other ways to solve this same problem too and we come back to some functional features of python in addition to functions like map there's also this one called filter that can be used
to achieve the same effect but with a more functional approach if you will let me go back to vs code here and with the same example let me do this let me leave the original list up above as before including draco who's not in fact from gryffindor and let me temporarily define a function called is gryffindor that takes in his value something like a student s and then let's do this let's go ahead and say if s quote unquote house equals equals gryffindor then go ahead and return true otherwise go ahead and return false now
we've seen before conditionals like this that are a bit unnecessarily verbose i don't need to have a conditional if i'm already asking a boolean question up here so i can actually tighten this up as we've done in the past and just return does the student's house equal equal gryffindor either it does and it's true or it doesn't and it's false i don't need to explicitly return true or false i can just return the value of that boolean let's go ahead now and do this i'm going to create as before a variable called gryffindors a list
for all of my gryffindor students that equals to this time the result of calling filter filter takes at least two arguments here one of which is the name of a function to call is gryffindor and i'm going to apply that function to each of the elements of this sequence here so similar in spirit to map i'm passing in a function that's going to be applied to each of the elements in the sequence but map returns one value for each element in the sequence that's how we forced all of the words to uppercase but if i
want to conditionally include a student in my resulting gryffindors list i can use filter instead filter expects its first function to be not something like stir dot upper but a function that returns true or false tell me whether or not i should include or not include the current student from the final list and the question being asked is do they live in gryffindor we're checking the dictionary's house key for that answer and so ultimately i think we'll be left with something quite similar for gryffindor in the sordid ver let's do for gryffindor in gryffindor's let's
go ahead then and print out the current students gryffindor name it's not going to be sorted just yet but when i run this version here python of gryffindor stop pi and hit enter we're back in business it's unsorted but we have hermione harry and ron but not draco and if you recall from a few weeks back if we want to sort even a list of dictionaries we can still do that too i can call sordid on gryffindor's plural and i can pass in a key and that key can have a anonymous function aka a lambda
function that takes in a student as input call it s and then returns the value s quote unquote name if my goal is to sort by indeed students own names if i go ahead now and run python of gryffindor's dot pi i see the same list of students but this time it's sorted so here we've seen two approaches to this particular problem of gryffindor students whereby we can either use something like a list comprehension and inside of that list comprehension do a bit of filtration including an if conditional as i did or we can take
a more functional approach by just using this filter function passing into it the function that i want to make these decisions for me and then include only those for whom true is returned any questions on either of these two approaches uh yeah i just had a question that if we write the code like in the previous version where everything is stuffed into one like once the if we check for the style of the code then won't it have a pro don't have a problem with it because it's less readable so with a formatter like black
have a problem with the style of some of this code the previous one where the everything was stopped into one language oh a good question would something like black have a problem with this code well let me rewind to that version which was using the somewhat longer list comprehension which looked like if we go far enough back give me a few more undo's which looked like this ultimately let me go ahead and run black on gryffindors.pie and you'll see that i actually it reformatted ever so slightly but i proactively fixed this myself had i done
this and done it on just one line but i knew that black might not like that it would have fixed it for me so i just proactively fixed it before writing the code myself how about time for one other question on gryffindor's dot pi and this approach of using a list comprehension or filter yeah when when using filter instead of calling the function script finder can you use it right there inside filter can you use the function is gryffindor so you don't want to call it like this because you don't want to call it then
you want filter to call the function for you if that's what you mean so i pass it in only by its name instead [Music] no i mean if you can write the return as house equals equals grip in the inside yes indeed in fact so recall that we indeed used these lambda functions way back when when we wanted to pass in a quick and dirty function anonymously to allow sorted to filter by a different key of a dictionary we can do that here i can actually take the essence of this ish gryffindor function i can
change the name of this function in my filter call to be another lambda function passing in an argument like s and returning exactly that i can now delete my is gryffindor function altogether and now when i run python of gryffindor's dot pi i still get the same answer and i've not bothered defining a function only to then use it in one and only one place well let me propose too that we equip you with one other tool for your toolkit namely dictionary comprehensions as well and admittedly the syntax is starting to get even weirder but
as you get more comfortable with all of these primitives and others these are just tools that you can optionally but perhaps powerfully use to solve future problems down the road and with a dictionary comprehension we have the ability to create on the fly a dictionary with keys and some values without having to do it sort of old school by creating an empty dictionary and creating a for loop and iterating over that loop and inserting more and more keys and values into the dictionary we can rather do it all at once so in fact let me
go back to vs code here and let me propose now that i do this let me go ahead and initially do it the old-fashioned way here as follows let me go ahead and simplify and get rid of the houses all together so that we can focus for now just on a list of students names i'm going to go ahead and run students i'm going to go ahead and write students equals quote unquote hermione quote unquote harry and we'll keep it even shorter this time quote unquote ron only those three students in gryffindor i'm going to
now proactively as we've done in the past give myself an empty list so that i have something to accumulate some answers to this problem in and now i'm going to do something like this for student and students so i can iterate over each of them let's go ahead and with the gryffindors list append to it the name of the student so quote unquote name and then student which is indeed their name from that list and now let's go ahead and just put these students all in gryffindor i know these three students are in gryffindor so
suppose that the problem at hand is that i want to build up a list of dictionaries that only contains the gryffindor students so it's sort of a step back from the previous version where i already had the names and the houses for now just assume that the problem is i have all of their names but i don't yet have the student dictionaries themselves so i'm rebuilding that same structure that i previously took for granted now let's go ahead and just for the sake of discussion just print out these gryffindors so we can see what we've
built if i run python of gryffindor's dot pi in my prompt i see a bit of a cryptic syntax but again look for our little hints i've got a square bracket at the end and a square bracket at the beginning and that indicates as always this is a list i then i have a whole bunch of curly braces with a whole bunch of quoted keys they happen to be single quotes by convention when using print on a dictionary but that's just a visual indicator that that is my key and the first value thereof is hermione
second key is a house this value thereof is gryffindor then there's a comma which separates one object from the next and if we look past harry and gryffindor there's a second comma which separates harry and gryffindor from ron and gryffindor as well so in short here is some code whereby i fairly manually built up with a for loop in an otherwise initially empty list the same data structure as before minus draco just for gryffindor students but here's where again with dictionary comprehensions or really list comprehensions first can we do this a little more succinctly let
me clear my terminal window let's get rid of this initially empty list and this for loop that appends appends appends to it and let's just do this a gryffindor's variable will equal the following list comprehension inside of that list i want a dictionary structured with someone's name and their name someone's house and only for now gryffindor and that's it but i want one of these objects here in these curly braces for each student in students so here too inside of my list comprehension with my square brackets i want an object as indicate i want a
dictionary as indicated by the curly braces i want each of those dictionaries to have two keys name and house respectively the values thereof are the student's name from earlier here and gryffindor only which students do i want to create those dict objects from well for student and students so again on the left i have what i want in the final list and on the right i have a loop and this time no conditional i want all of these students in gryffindor as their house now let's print this again uh python of gryffindor's dot pi and
hit enter and now we have the exact same output so instead of three lines it's just one it's a little more cryptic to read at first glance but once familiar with list comprehensions and this sort of syntax it's just another way of solving that same problem what if i want to change this and simplify what if i don't want a list of dictionaries which i now have again per the square brackets i have a list of three dict objects here what if i just want one bigger dictionary inside of which is a key like hermione
colin gryffindor harry colin gryffindor ron cole and gryffindor i don't need a list i don't need separate objects per student i just want instead one big dictionary where the keys are the students names and the values of their house and i'm assuming for now no one's going to have the same first name in this world well i can do this let me get rid of this here and not create a list comprehension but again this thing known as a dictionary comprehension and the visual indicator or difference here is that instead of being square brackets on
the very outside this time it's going to be curly braces instead so inside of these curly braces what do i want every key to be i want every key to be the student's name i want every value for now to be gryffindor and i want to do this for each student in students and now things are getting really interesting and this is another manifestation of python in some views being very readable from left to right absolutely takes practice and comfort but this is creating a variable called gryffindor which is going to be a dictionary per
these curly braces every key is going to be the name of some student every value is going to be gryffindor what names of what students well this dictionary comprehension will be constructed from the list of students one at a time so when i print this now the syntax will look a little different because it's not a list of dictionary objects it's just one bigger dictionary object itself but now printing gryffindors gives me hermione colin gryffindor harry cole and gryffindor and ron colin gryffindor as well any questions now on what we've called dictionary comprehensions as well
[Music] any questions on here no well let's introduce one other function from python's toolkit followed by one final feature and flourish and then you're off on your way well let's go ahead and think back to this recall some time ago that we had just a simple list of students as we have here hermione harry and ron and for instance way back when we wanted to print out for instance their ranking from one to two to three unfortunately when you do something like this for student in students you can print out the student's name quite easily
of course if i do python of gryffindors.pie i get hermione harry ron in that same order but i don't see any numerical rank i see no number one two or three so i could maybe do this with maybe a different type of for loop instead of this why don't i try this so maybe i could do for i in the range of the length of the students list and we've done something like this before and then i could print out i and i could print out the student's name by indexing into that list at location
i well what does this look like if i run python of gryffindor's dot pi it's close but you know these aren't programmers they don't necessarily think of themselves as zero index hermione probably wants to be first not zero so how can we fix this well just a little bit of arithmetic i could print out i plus one of course and then the student's name so if i clear my terminal window and run python of gryffindor pi once more now we have this enumeration one two three of each of these students but it turns out that
python actually has had all this time another built-in function that you might now find useful that is namely enumerate and enumerate allows you to solve this kind of problem much more simply by iterating over some sequence but finding out not each value one at a time but both the value one at a time and the index thereof it gives you back two answers at once so if i go back to vs code here now and take this approach i don't need to do this this complicated range and length and then i all over the place
i can more succinctly do this i can say for i comma student in the enumerate return value passing in students so this gives me back an enumeration if you will and now i can go about printing i plus 1 as before and i can print out the student so i don't need to index into the list with bracket i notation i don't need to call range i don't need to call length again enumerate takes a sequence of values like these students and it allows me to get back the current index 0 1 2 and the
current value hermione harry ron respectively so now it just tightened things up further and indeed that's been our theme here can we solve the same problems as we've been solving for weeks but tighten things up using just more of this toolkit allow us to equip you with one final tool for your toolkit namely this ability to generate values in python from functions this is not a problem that we've necessarily encountered before but it turns out if you're writing a function that reads or generates lots of data your function your program your computer might very well
run out of memory and your program might not be able to run any further but it turns out there's a solution to this problem that's something you might have in your back pocket particularly if after this course you start crunching quite a few numbers and analyzing all the more data in fact let's go back to vs code here and let's go ahead and create a program that's perhaps timely at this time of day particularly depending on your time zone you might be feeling all the more sleepy but here in the us it's quite common to
be lulled to sleep when you're struggling otherwise by counting sheep in your head and typically as depicted in cartoons you might see in your mind's eye one sheep jumping over a fence and then two and then three sheep and then four and then eventually you presumably get so bored counting these sheep you actually do fall asleep so in vs code here let's create a program called sleep dot pi that allows me to print out some number of sheep as though i'm counting them in my mind's eye and via this program let's do this let's prompt
the user for a variable n setting it equal to the integer conversion of the return value of input asking the user what's n for how many sheep do they want to try counting and then let's do a familiar for loop here and we'll start counting from zero as always so we'll first have zero sheep then one sheep then two sheep and so on for i in the range of that value n go ahead and print out and i'll paste here an emoji representing a sheep times i so the first iteration i'll see zero sheep the
second iteration i'll see one and then two and then however many uh specified by n ultimately minus one all right let's go down into my terminal window here and run python of sleep dot pi and i should see indeed after typing in say three for my value of n zero sheep then one sheep then two sheep and so forth and if i make my terminal window even bigger here we can of course do many more than this typing in for instance 10 and you'll see that we get more and more sheep as time passes presumably
becoming all the more tedious to envision in my mind's eye so let's now go ahead and practice what we've been preaching when it comes to the design of this program and see if and when we actually run into a problem let me go ahead here now and put all of this in a main function by defining main up here as always let me go ahead and indent all of this code here and then let me just do this conditionally as always if the name of this file equals equals quote unquote main let's go ahead and
call main let's make sure i didn't break anything just yet even though functionally this should be nearly the same and if i type in three i still have zero then one then two sheep on the screen but we've been in the habit of course of creating helper functions for ourselves that is factoring our code in a way that allows us to abstract away certain functionality like uh generating some number of sheep into separate functions so that one they're indeed abstracted and we no longer have to think about how they're implemented and we can even reuse
them in projects as in libraries but we've also been in the habit too of now testing those functions as with unit tests so i probably shouldn't keep all of my logic anyway in main and let's factor some of this out wouldn't it be nice if i could for instance just call a sheep function as by taking this line of code here and instead of just printing it here let's print out the return value of a new function called sheep that tells the function how many sheep to print i in this case let's go down as
always and create another function here called sheep the sheep function now will take a parameter n that specifies how many sheep do you want to return and so that we can test this as with a unit test though we won't do that here let me go ahead and not print the number of sheep as by a side effect but let me go ahead and return one of those sheep times n so that the user gets back a whole string of sheep that's the appropriate number to print so here too functionally i don't think we've changed
anything too fundamentally python of sleep dot pi typing 3 still gives us 0 then 1 and then 2 sheep but now we at least have a framework for focusing on the implementation of this sheep function but it's a little inelegant now that it's still up to the main function to do this iteration we've seen in the past way back in week 0 wouldn't it be nice to define a function that actually handles the process of returning the entire string that we want rather than just one row of sheep at a time well i think we
can do this why don't i go ahead and change sheep as follows let me go ahead here and first create a flock of sheep that's initially empty using an empty list then for i in uh the range of n let's go ahead and append to that flock for instance one sheep times i so that i keep adding to this list zero sheep then one sheep then two sheep then three and so forth and then ultimately i'm going to return the whole flock of sheep at once so this is going to return the equivalent of all
of those strings of sheep so that ah maine can handle the printing thereof so back up here in maine let's do this how about for each sheep i'll call it s since sheep is both singular and plural for s in sheep of n which again returns to me a list of all of the sheep the whole flock let's just print out each sheep s one at a time all right so so far so good here i think let me go ahead and run python of sleep dot pi and hit enter what's n3 and that still
seems to work just fine but let me get a little creative here and see not just three sheep on my screen but maybe 10 rows of sheep and that too seems to work fine let me get a little more adventurous and type in maybe 100 sheep and it's starting to look ugly to be fair but they're all printing out pretty fast let me go ahead and try again with maybe 1 000 sheep on the screen and they flew by pretty fast it's still pretty messy but they're all there we could count them all up how
about not just a thousand but 10 000 sheep well that too seems okay it's taking like 10 times as long and that's why you see this flickering on the screen all of the sheep are still printing but but but it's a lot of data being printed if i hang in there a little longer hopefully we'll see all ten thousand sheep coming to pass this is here in the video where we'll we will speed up time a la a real online oh my god this is a lot of sheep there we go okay and now all
of my sheep have been printed so it seems to be working just fine well let me just be even more adventurous and okay let me try my luck let me try like how about one million sheep this time and hit enter huh something's no longer working while we wait for a spoiler here does anyone have any intuition for why my program suddenly stopped printing sheep what is going wrong in this version wherein i'm generating this really big flock of sheep we might have run out of memory or computation power yeah so maybe we're actually pushing
the limits of my mac my pcs my cloud servers memory or cpu the brains of the computer's capabilities because it's just trying to generate massive massive massive lists of sheep one million of those rows of sheep each of which has a huge number of sheep and it seems that my computer here is honestly just really struggling and this is really unfortunate now because it would seem that even though this program clearly works pretty well for a thousand sheep 10 000 sheep once you cross some threshold it just stops working all together or it just takes
way too long for the program to be useful anymore but this seems a little silly right because theoretically i should absolutely be able to print all of these same sheep if i just printed one right away then print two right away then print three then four then five it seems that the essence of this problem if i go back to my code is that per my best practices that i'm trying to practice what i'm preaching it seems that the fundamental problem is that i've modularized my code by creating this helper function called sheep whose purpose
in life is to do all of the generation of sheep and then return all of them at once wouldn't it be better and i can actually hear my fan turning on now even just trying to generate these sheep wouldn't it be better than to just print the sheep one two three four at a time well we could do that but that's really a step backwards that rather contradicts all of the lessons learned of the past few weeks where generally not putting everything in maine is a good thing generally having an additional function that you can
then test separately with unit tests is a good thing do we really need to give up all of those best practices just to print out some sheep and and here fall asleep well it turns out there's a solution to this problem and namely in the form of these generators in python you can define a function as a generator whereby it can still generate a massive amount of data for your users but you can have it return just a little bit of that data at a time and you yourself can implement the code in almost the
same way but you don't have to worry about too much getting returned all at once these two like all features of python are documented in the official documentation they're in but what you'll find ultimately that it all boils down to this keyword here yield up until now when we've been banking functions we have been defining functions that return values if at all using the keyword return and indeed if we go back to our code here that's exactly what i've been waiting for i've been waiting to return the whole flock at once unfortunately if you wait
too long and here we have it my program was quote unquote killed that is to say my computer got so fed up with how much memory and cpu it was trying to use it just said nope you're not going to run at all and that's unfortunate now my program no longer works for large numbers of sleeps sheeps which is not good if i'm really having trouble falling asleep some night so how can i use yield to solve this problem instead well let me do this instead of building up this massive list of sheep in this
big list called flock let's just do this instead let me go ahead and simplify this whole function as follows whereby i iterate for i in the range of n and then on each iteration in the past i might have been inclined to use return and return something like one sheep times i but this won't work here right because if you want a million sheep and you start a for loop saying for i in the range of a million you're going to return accidentally zero sheep right away and then this function is essentially useless you shouldn't
return a value in the middle of a loop like this because you're not going to get to any of these subsequent iterations of the loop it's going to iterate once and boom you return but thanks to this other keyword in python called yield you can tell python to effectively return just one value at a time from this loop so if i go back to this version of my code here and i say not return but yield this is like saying return one value at a time return one value at a time return one value at
a time the for loop will keep working and i will keep counting from zero to one to 2 all the way up toward 1 million but each time the function is just going to hand you back a little piece of data it's going to generate so to speak just a little bit of that data not all of the data at once and that's good because my computer has a decent amount of ram certainly enough to fit one row of sheep it just doesn't have enough memory to fit apparently one million rows of so many sheep
so now if i go to my terminal window and run python of sleep dot pi and hit enter what's in 3 would still work 0 then 1 and then 2. let me go ahead and increase the size of this here and run python of sleep dot pi let's try 1 million as before and hit enter and now i immediately see results i don't think we'll wait for all of these sheep to be printed because then we will literally all be asleep but what you'll notice happening now is the program is not hanging so to speak
it's not waiting and waiting and thinking and thinking and trying to generate the entire flock at once it's just generating one row of sheep at a time and it's flickering on the screen because there's so many of them and that's all thanks to yield it's generating a little bit of data at a time not all at once any questions now on this feature called generators [Music] any questions at all to add one more piece of terminology to the mix just so you've heard it this same feature of this same feature here is returning what we'll
technically now call an iterator yield is returning an iterator that allows your own code your own for loop in main to iterate over these generated values one at a time how how does this yield actually works under under the hood i mean is it is it using multi multi trading you can think of the implementation as being asynchronous in this sense whereby the function is uh returning a value immediately and then subsequently giving you back another one as well underneath the hood what's really happening is the generator is just retaining state for you it does
not going to run the entire loop from top to bottom and then return a value it's going to do one iteration and yield a result and the python for you is going to suspend the function if you will but remember on what iteration it was so the next time you iterate over it as is going to happen again and again in this for loop in main you get back another value again and again so yield returns indeed this thing called an iterator and that iterator can be stepped over as in a loop one element at
a time but the language python handles all of that for you so that you don't need to do all of the underlying uh plumbing yourself how about time for one other question on these generators and iterators as our sheep continue to fly by so in every iteration the program will return the memory to the system so the program will not crash correct on each iteration it's only returning the one string of sheep that's appropriate for the current value of i it is not trying to return all million rows of the same and therefore it uses
really one millionth the amount of memory although that's a bit of an oversimplification all right as the sheep continue to fly across the screen let me now uh go ahead and interrupt this as you might have had to in the past with infinite loops in your own code even though this is an infinite it's just really long ctrl c will interrupt with your keyboard that program giving me back control of my computer well here we are at the end of cs50's introduction to programming with python and if today in particular of all days felt like
a real escalation real quickly realize that these are really these are just additional perhaps optional tools in your toolkit that you can add to all of the past lessons learned so that as you exit from this course and tackle other courses or projects of your own you have all the more of a mental model and all the more of a toolbox with which to solve those same problems if we think back now just a few weeks ago it was probably in our focus on functions and variables that you first started struggling but now in retrospect
if you look back at those problems and those same problem sets odds are those same problems would come all too easily to you now conditionals was the next step in the class where and we gave you the ability to ask questions and get answers and therefore do things conditionally in your code we came full circle today and you can see that you can now use those same kinds of conditionals now to do fancier things with list comprehensions and dictionary comprehensions and the like loops of course have been omnipresent now for weeks including today as we
built up those same structures and of course something can go wrong and exceptions and exception handling was our mechanism for not only catching errors in code but also raising your own exceptions so that if you're laying the foundation to write code for other people as in the form of libraries you can do that too libraries of course are things you can not only use but now write on your own be it a small module or whole package of code that you want to share with others around the world and even better can you write tests
for your own code for your libraries for others code as well so that ultimately you can be all the more confident that not only your code is correct today but if you make a change to your code tomorrow you haven't broken anything at least according to your tests if they continue to pass file i o though meanwhile was a way of now storing data not just in the computer's memory like all of these sheep but actually storing things persistently longer term to disk being in a csv or something more like a binary file like an
image with regular expressions you then had the ability to express patterns and actually validate data or extract data from information all the more of a useful technique nowadays when so much of the world is trying to analyze and process data at scale some of which might in fact be quite messy from the get-go and then of course most recently object-oriented programming an opportunity to solve the same kinds of problems but with a slightly different perspective a way to encapsulate and to represent real world entities this time in code and today of course etc was so
many other tools that you can add that didn't necessarily fall under any of those earlier umbrellas but are useful functions and data types and techniques just to have again in your back pocket as yet other mechanisms for solving problems as well not just putting everyone to sleep but i thought another way to end might be a little more vocally to try writing one final program together this one using a library we've seen in the past as well as one other i've taken the liberty of installing a text to speech library on my computer here and
i'm going to go ahead perhaps and open a new file here called uh say dot pi in vs code and i'm going to go ahead here and first import our own friend import cow say i'm going to import this new library here import pi ttsx3 the python text to speech library and now per its documentation which i read in advance i'm going to go ahead and create a variable for myself here engine equals y t t s x three dot in it to initialize that library for text to speech i'm gonna then ask the user
well what do i want to hear spoken and i might do something like this a variable called this equals the return value of input what's this shall be my simple question and i'm going to keep it this time as a string we've seen how to use cow say we can do cow say.cal of this turns out this new library can allow me to use its own engine to say this as well but then ultimately i'm gonna have to run the engine.run and wait just in case it's a long phrase or sentence to be said but
that's it in just eight lines of code not only am i apparently going to have a cow appear on the screen to close us out now but also some synthesized text ultimately then we hope with this course that you not only learn python that you've not only learned programming but you've really learned how to solve problems and ultimately how to teach yourself new languages funny enough i myself only learned python just a few years ago and even though i certainly went through some formal documentation and resources online i mostly learned what i know now and
even what i had to learn again for today by just asking lots of questions be it of google or friends who are more versed in this language than i and so having that instinct having that vocabulary very which to ask questions of others to search for answers to questions you absolutely now have enough of a foundation in python and programming to go off and stand on your own so you can certainly and you're welcome and encouraged to go on and take other courses in python and programming specifically but better still as quickly as you can
is to find some project that's personally of interest that uses python or some other language because at least from my own experience i tend to learn best and i hope you might too by actually applying these skills not to problems in the classroom but really truly to problems in the real world allow me with all that said to look at my full screen terminal window here run python of say dot pi crossing my fingers one final time in hopes that i've not made any mistakes or bugs and here we go python of say pi prompting
me what's this how about we end on this note here [Music] this was cs50
Copyright © 2025. Made with ♥ in London by YTScribe.com