Python is an interpreted high-level general-purpose programming language.
The Python import system doesn't just seem complicated – it is complicated. So even though the documentation is really good, it doesn't give you the full picture of what's going on. The only way to get such a picture is to study what happens behind the scenes when Python executes an import statement. And that's what we're going to do today. Note: In this post I'm referring to CPython 3.9. Some implementation details will certainly change as CPython evolves. I'll try to keep track of important changes and add update notes.
Before we begin, let me present you a more detailed version of our plan. First, we'll discuss the core concepts of the import system: modules, submodules, packages, from <> import <> statements, relative imports, and so on. Then we'll desugar different import statements and see that they all eventually call the built-in __import__() function. Finally, we'll study how the default implementation of __import__() works. Let's go!
Consider a simple import statement:
What do you think it does? You may say that it imports a module named m and assigns the module to the variable m. And you'll be right. But what is a module exactly? What gets assigned to the variable? In order to answer these questions, we need to give a bit more precise explanation: the statement import m searches for a module named m, creates a module object for that module, and assigns the module object to the variable. See how we differentiated between a module and a module object. We can now define these terms.
A module is anything that Python considers a module and knows how to create a module object for. This includes things like Python files, directories and built-in modules written in C. We'll look at the full list in the next section.
The reason why we import any module is because we want to get an access to functions, classes, constants and other names that the module defines. These names must be stored somewhere, and this is what module objects are for. A module object is a Python object that acts as a namespace for the module's names. The names are stored in the module object's dictionary (available as m.__dict__), so we can access them as attributes.