 Python includes an important special module named builtins. So called because it includes dozens of commonly used builtin functions and classes, such as say, the print function which prints text on the console, a function called abs, or for absolute which returns the absolute value of a number, or say the classes like stir an object, these things are all placed in the builtins module. If you were to import builtins, that would assign to the name builtins, the builtins module object, and then if we write builtins.print, that would get us the function print from the builtins module. And so here if we write builtins.print with the argument 3, that would print the number 3 on the console. The special thing about the builtins module, however, is that we can access its attributes directly. We don't have to import it. So rather than writing import builtins and then builtins.print3, I can simply write print with the argument 3. The reason this works is because when you use a variable name, Python searches for that name first in the local scope of where we are, say if we're in a function. And then if it's not found there, it'll look in the enclosing scope, say any enclosing other functions. And then if it's not found in any of those, it then looks in the global scope, aka the module scope, the module. And if it's not found there, it'll then search in the builtins scope. So in Python we call this the rule of LEGB, local enclosing global builtins. When Python needs to resolve a name, first it looks in the local scope, then the enclosing scope, then the global scope, aka the module, and then the builtins module. The local enclosing global part you're already familiar with, that's basically how JavaScript works too, but the thing Python adds is that it then goes and looks in this fourth place, the builtins module. So again, generally when we want to access an attribute of the builtins module, we don't access it per usual as an attribute, we just refer to it directly by name. Now the way this works does mean it is possible to effectively obscure names from the builtins module. For example, if in the current module we were to sign to the name print some value, say here the value 9, now if we try and revoke print as a function, well print isn't going to be found in the builtins module, it's going to be found here in this module itself, and we're going to get an error here because you can't invoke a number as a function. Generally, Python programmers solve this problem by simply avoiding assigning to names found in the builtins module. An interesting thing to note here is that this is the only case where it's possible for the use of a variable name to actually change which scope it resolves to. Imagine, for example, that in some function we invoke print. As long as that function itself and none of its enclosing functions and its enclosing module have no variable themselves called print, that print in the function will resolve to print of the builtins module. But because modules are mutable, that is attributes can be added and removed, it's possible for that print in the function to change what scope it resolves to. If we add print to its enclosing module, then that print in the function will no longer resolve to print of the builtins module, it will resolve to print in its enclosing module. And in fact, the reverse can happen as well. If a name resolves to something from the enclosing module, if we delete that attribute from the enclosing module, then it might resolve to the something of the same name in the builtins module. So in short, it's possible for a name that resolves to something in the enclosing module to switch to resolve to the same name in the builtins module and vice versa. The switch can go the other way. This does not happen with the local variables of functions. The local variables of functions are effectively fixed in which scope they resolve to. Very briefly, we can see this happening in action. Here's a function foo, which invokes print from the builtins module. And if we invoke foo the first time, that's okay, it prints hello. But then if in that module we assign to print some number, something which is not a function, and then we try and invoke foo, well then print inside the foo function is going to resolve to print of the local module now, and that's a number, not something we can invoke, so this is going to trigger an error, it's an exception. So finally now, if we wanted to create the hello world program in Python, it would simply be a module, a single source file, a consistent of justice line, print with the argument hello world as a string. When we run the Python interpreter and we don't specify any module to load and run, Python then runs in interactive mode. In interactive mode, Python gives you a prompt at which you can type a statement of code, and then you hit enter and Python will run that code. Effectively, interactive mode is like as if we are writing a module of Python code line by line, and every time we hit enter, it runs that code immediately. The utility of this isn't really all that great. I do though like to use Python as my calculator, and this interactive mode does come in handy for people learning the language because it's nice that you can just start up the interactive mode and try a few things and see what happens. Notice here in this interactive mode, when I wrote a line that triggered an exception, normally an exception that isn't caught in Python will basically abort your program, but here in the interactive mode, it simply just prints out the exception message and then lets you continue on to type more statements. The last thing we'll talk about in any detail here is how Python treats exceptions, which are basically the same as we saw in JavaScript except a few terms have changed. First of all, what JavaScript calls a catch clause in Python is called an accept clause, and where JavaScript has a throw statement, the Python equivalent is called raise. In Python, we usually say an exception is raised rather than thrown. The big difference in Python is that a try may have multiple accept clauses, and when an exception is thrown in the try, the accept clause which executes is the first one, which lists a class of which the exception object itself is an instance. So here in the example, we have a try with three accept clauses. The first accept clause lists a class Amy, the second has two classes Carol and David, and the third accept clause doesn't list any classes, which means that it will match any exception. So if in the try block here, an exception is thrown, Python tests whether or not that exception is considered an instance of the Amy class. So it must either be an instance of the Amy class directly, or it must be some instance of a descendant of Amy. And if that test proves true, if the exception is an instance of the Amy class, then that first accept clause is the one that runs. Failing that, however, Python then goes on to test the subsequent accept clauses. And so here it will test if the exception is an instance of either the Carol class or the David class or both. And if it matches either one of those or both, then that accept clause runs. But then failing that, if we get to this last accept clause that doesn't have any class specified, well, this accept clause will match any exception object. So this effectively serves as the default exception clause. To restate all that, when an exception is thrown in a try, Python looks for a matching accept clause in order top to bottom testing by type. And when a match is found, that is the accept clause which runs. In a case where no accept clause matches, then the exception will propagate. And finally, the last thing to say about exceptions here is that whereas in JavaScript, an exception can be an object of any type, and Python exception objects must be instances of the base exception class. That is, they must directly be instances of that class or they must be instances of some descendant of the base exception class. That aside, the big new idea with exceptions in Python is that an exception thrown in a try is matched to an accept clause by type. That's the one big conceptual change. We have a few more things to say about exceptions which we'll cover in the supplementary material.