 An important thing to know is how Python actually discovers how it actually finds the module specified in an import statement. Well, the answer is that Python keeps an internal list of directories, and when you import a module, Python searches for a module of that name in these directories in order from first to last, so the order in which the directories appear in the list can actually matter. You can access this list by importing the sys module, sys is short for system, and as the name implies, the sys module is a standard Python module that contains things related to the system itself, the environment in which Python is operating, and this module's attribute path references the list of directories which Python uses when it searches for a module. Now, you can actually modify this list, and if you do, that will affect any future imports. So here we import the sys module, and we're appending a directory path, slash home slash brian, to the end of sys.path, so now in subsequent imports, Python will look in that directory to find modules, though this is being appended to the end of the list, so it's the directory which will be searched last, which again can matter, because if we're searching for a module, say named foo, and if there's a foo module in more than one of the directories in sys.path, well whichever one is found in first, that's the one Python will use. Now, as for what directories you'll find listed in sys.path by default, that depends on the platform, it'll differ in Windows from in Linux, but the gist is that on any platform by default you'll find in sys.path directories which contain modules from the standard Python library, so you should be able to import standard library modules without having to modify sys.path. Also understand that the directories listed in sys.path can be expressed either as absolute paths or as relative paths, and the relative paths are interpreted as relative from the current working directory of the Python interpreter process. And in fact if you start up Python in interactive mode, if you start up the Python show, you'll find that the first path listed is just an empty string, which is effectively a relative path to the current working directory itself. So in effect when you run Python in interactive mode, the current working directory is always going to be the first directory searched by imports. If however you run Python not in interactive mode, but by specifying the name of some Python module to run as your main module, then the directory of that module will be the first directory listed in sys.path. And that makes sense because normally the other modules of a program are placed in the same directory as the main module, or they're placed in subdirectories of that directory. Now Python module files actually come in two forms. There's the .py files, which are source code files, and the ones that you write, but when Python takes a source module, a .py file, it creates from that a compiled Python module called a .pyc file, or a .pyc file. And the idea of these .pyc files, these compiled Python modules, is that they're faster to load, that's the main purpose. The term compile here is a bit of a misnomer because it's not like these files contain native code, and they don't really contain something like Java byte code either. Really what it is is simply just to say, when you execute a Python module, the whole parsing of that source code and the creation of the functions representing the objects in the classes, well, that business can all take up a surprising amount of time at program start, so .pyc files are basically pre-parced modules with serialized function and class objects, which can just be read from the file into memory without having to actually produce those objects. And that can save quite a bit of time in processing on program startup. Now, you don't actually have to worry about creating these .pyc files yourself, because the default behavior in Python is to just automatically create them for you. The way this works is that when Python searches for a module, there are basically three cases. There's the case where only .py exists, the regular source file. Then there's the case where Python only finds a file named .pyc and no corresponding .py file, and this situation might occur because someone could create a Python program and only distribute the .pyc files rather than the source code. And then there's a third scenario where both files exist, where Python finds both .py and .pyc. Well, in the first case, when only .py exists, the source file exists, then Python will actually create the .pyc file from .py and then, of course, execute the module. In the second case, where only the compiled form is found, the .pyc file is found, Python will simply, of course, just load the module from the .pyc file. But then in the case where both files exist, then Python checks whether the .py file is newer than the .pyc file. That is, if the source is newer than the existing compiled module. And if that's the case, well, then obviously what's happened is the programmer has updated their source code and then, of course, they want to have the compiled file updated to match. So Python will recompile the module. It will update the .pyc file before then actually loading it. So the way this generally works out is that if you have a Python program all in source form and then you run the program, then that first time the program runs, Python will produce all the corresponding .pyc files such that any subsequent time you run the program, it will be loaded directly from the .pyc files instead of having to reparse all the source code and compile it again. The only exception being if you update, if you modify any of the source files in Python, we'll notice that whichever source code files you updated now have a newer date modified date than the .pyc files. So it will then update those .pyc files. Which is convenient because effectively what this means is that when you're working on a Python program, you really don't have to worry about manually doing any of this compiled business that all just is handled for you and if you modify the source code, you don't have to worry about it accidentally using any of the outdated compiled form. It will just recompile the code as necessary. What Python calls a package is basically just a directory of module files, all of which presumably relate to each other somehow. It's just a mechanism of grouping your modules into logical groups. In an import statement, we refer to the modules of a package using the .operator, even though this really has not much at all to do with a tribute lookup or anything like that. So here we have import foo.bar, this means to import the module bar found in the package foo. So Python will search sys.path for a subdirectory foo with a module bar in it. Now the complication of packages is it's not just a directory containing the modules of that package, it also contains another file given the special name double underscore init double underscore dot py. And the special init module of the package is really just a module like any other, it just has a special name and you can put actually any code you want in init dot py really just like any other module, though you could just leave them blank as I usually do. But then the special thing about the init module is that when you import the modules of that package, they get assigned as attributes into this init module. So here for example, when we import foo.bar, what actually happens in the local namespace where we're importing foo.bar is that the name foo is assigned and nothing else. And here after the import, the name foo refers to the module object representing the package itself, the init.py file in that directory. And because we've imported the module bar of that package, foo.bar will resolve to that module. However, bar itself is not found directly in the local namespace. Also any other modules of the package foo won't yet be found in the module object representing foo until such time as those modules themselves are explicitly imported. In fact, it's quite possible to import just the package itself without importing any of the modules contained therein. So here we import just the package foo itself and until we explicitly import the modules of the package itself like foo.bar, those modules won't be found in the package object, which is really just a module object. It's a bit confusing because the objects which represent the packages are really just regular module objects just like those representing normal modules. Now of course we can have directories within directories so we can have packages within packages. So here for example import eagle.wale.tiger.frog. This imports a module frog from a package called tiger from inside a package called will inside a package called eagle. Though actually as we just saw you can actually directly import packages themselves so we can't say for certain just looking at this line of code that frog is actually a module and not a package. Just remember that when you create a package directory it always must contain an init.py file even if you just leave it blank and there are no modules in the package. Now because some module and package names are obnoxiously long it makes sense that after import you'd want to assign these modules to some more convenient name. Like here for example if we import a module called supercalifragilisticxpialidocious we can just assign it to an abbreviated name and use that instead to refer to the module. This is done commonly enough that Python has an optional clause in an import statement called as. Here if we import supercalifragilisticxpialidocious as sup then it's importing the module called supercalifragilisticxpialidocious but rather than assigning it in the local namespace with that name it's assigning the module object to just the variable sup. So if we try and use sup then that results in the module object supercalifragilisticxpialidocious actually doesn't result to anything in the local namespace so it would be an exception to try and use that name. As a similar convenience sometimes when dealing with modules we're really just interested in one or two attributes from that module so there's the from import statement which imports the specified module just like in a regular import statement but the module object itself is not actually assigned to any attribute in the local namespace rather just the specified attribute of the module is assigned to the local namespace. So here for example from lobster import chicken means we want the attribute chicken from the module lobster so lobster is imported but not assigned to the local namespace but rather chicken is so we can use chicken directly we don't have to write lobster.chicken we can just write chicken and if we try and use lobster that's an exception because we didn't actually assign the module into the local namespace and in a single from import statement if you want multiple attributes you just list them separated by commas so this imports three attributes from lobster chicken turtle rabbit and it assigns all of them directly into the local namespace and if you want to import an attribute but not assign it by its given name in the module but by some alias you choose you can do so by using from import as so here this is importing the attribute turtle from the module lobster but instead of assigning it as the name turtle into the local namespace it assigns it as T so T now is a local name referring to the object held in the attribute turtle of the module lobster now a very important thing to keep in mind when using module attributes this way is that when you sign them directly into names of the local namespace that name is really just an ordinary variable pointing to whatever object was in that attribute at the time of the import if you then change the attribute of the module this local reference to it is not automatically going to get updated so if turtles say were some module attribute which you expect to change throughout the course of the program you wouldn't want to use it via from import you'd want to always access it as the attribute of the module you'd want to import the module itself and then always write lobster.turtle to use that value because that's the only way you get always the most up to date value of the module attribute finally there's one more convenience with from import where maybe you just want to directly import all of the attributes of a module into the local namespace this is convenient but it does suffer from the problem I just described where we're really just creating new local variables that might get out of date with the actual values of the module attribute it's also generally considered bad practice to use this asterisk because it means that you're just dumping all the names of some module into your local namespace and basically polluting your local namespace and many of them which you probably aren't going to use it also tends to wreak havoc because the module you're importing someone else or even yourself might modify that module and that then changes what names get imported and that can easily cause unexpected name conflicts and those name conflicts could cause some pretty obnoxious bugs that are hard to track down they'll be sitting there looking at your code scratching your head thinking hey I didn't really change anything why the hell doesn't this work like it did before I just wanted to find out that oh well now there's this name foo in this module which is being imported into the local namespace thereby obscuring this other foo which I imported from this module just in the line above so generally I would say just avoid using this asterisk form I would only use it in an interactive session the last thing to discuss about imports is what's called a relative import which is an import that features a dot used to stand alone where the module usually goes the dot is prefixed to the partial name of a package in the same spot in the from import the idea of a relative import is that we're telling Python where to find our module relative to the current module the module in which we are using the from import statement so here for example assuming that we're inside a module of the package tiger dot whale well if you see from dot import turtle that's saying in the package tiger dot whale import the module turtle and then in the next line if you see from dot eel import newt that's saying from tiger dot whale dot eel import newt so in between from an import we're specifying a relative package that is a package relative from the current package and then after imports we're specifying the name of a module in that package and notice that this is really very different from what a from import normally does a from import normally is about importing specific attributes from some particular module whereas here we're importing modules from a particular package now it's possible in these relative imports to use multiple dots and whereas a single dot is used to signify the current package two dots is used to signify one level up so in tiger dot whale two dots means just the package tiger so from dot dot import newt means from tiger import newt in the context of the package tiger dot whale and then from dot dot eel import newt means from tiger dot eel import newt so again from tiger dot whale we went up one level to tiger and then we went down to eel inside tiger so this multiple dots is imitating the syntax you see with file paths where dot dot slash is used to go up one directory except here we're not using any slashes so if you want to go up two levels rather than just one you write three dots if you want to go up three levels you write four dots and so forth so the advantages of using relative imports are basically to fold it it makes it easier for you to rename your packages because when you rename a package directory you don't have to then go into all the modules of that directory and change all the import statements where one module of the package is using another module of that same package with relative imports you don't have to do that because they're all just referring to other modules of the same package using the dot syntax where the name is left unspecified relative imports don't completely solve the problem of package renaming but they do help a bit