# Day 18: Imports Welcome to day 18 of the 30 Days of Python series! Today we're going to be talking about the very important topic of imports.

So far we've been working with the core Python syntax, and we've been using the so called built-in functions. However, there's a lot more to the Python standard library than just these functions, but to get access to it, we need to be able to import this functionality.

Importing will also let us use 3rd party libraries, which we'll cover later in the series.

## §Basic imports

We're going to start off by importing the `math` module, which, as the name implies, contains a lot of useful tools for performing mathematical operations. It also includes several useful constants. You can find documentation here.

To import a module we need to write the `import` keyword, followed by the name of the module we want to import. To import the `math` module, we'd write this at the top of our code:

``import math``

Just like that, we have access to all of the functionality defined in the `math` module. In order to use these functions and variables, we just need to put `math.` before the function or variable name. This tells Python that we're requesting something located in the `math` module.

Let's see this in action with an example.

All the way back in day one of the series, we had the following exercise question:

Calculate and print the area of a circle with a radius of 5 units.

This would be a perfect application for the `math` module, as we can make use of the `math` module's `pi` constant:

``````import math

print(math.pi * 5**2)  # 78.53981633974483``````

This is a great deal more accurate than my original solution, which used `3.141` as the value for pi.

Now let's try a function instead. The `math` module has a function called `fsum` which is for adding together floating point numbers. You can certainly use regular old `sum` for this, but things get a little bit weird for certain values, because there's an inherent inaccuracy in floating point numbers.

Take the following example:

``````numbers = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
print(sum(numbers))  # 0.9999999999999999``````

That's right. Adding ten lots of `0.1` is not `1`. You'll get similarly dodgy results if you try adding `0.2` over and over again as well.

However, if we use the `fsum` function, mathematics goes right back to normal:

``````import math

numbers = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
print(math.fsum(numbers))  # 1.0``````

If you're interested to know why `0.1` produces this unusual result, I recommend you check out my post on the Decimal module. At the start I talk about how decimal numbers are represented in a computer, and why this leads to problems with floats.

### §Style note

Make sure to always put your imports at the top of the file! This is not only good style, there are also good practical reasons for this, which we'll get to shortly.

It's also a good idea to put a space after you're done with your imports to separate them from the rest of your code.

## §Importing specific things from modules

Let's say we're working on a project and I really only need the `pi` constant from the `math` module, and I plan to use it extensively in my code. There's no real problem with using `import math` and then referring to the constant using `math.pi`, but this is going to get a little tedious.

In this situation it would be a lot better if we could just use `pi` in our code, and we can do this by importing just the `pi` constant from the `math` module. The syntax for this is as follows:

``from math import pi``

We can import several things from a module by separating the names with commas. Let's say I also want the tau constant:

``from math import pi, tau``

Now we can refer to `pi` or `tau` directly instead of using `math.pi` and `math.tau`. However, we no longer have access to the rest of the `math` module using this approach, so we can't call `math.fsum` unless we also import `math`.

In some modules there are also things we can only access through this specific import syntax.

For example, let's say we want to work with `tkinter`, because we've decided we want to make an application with a graphical user interface (a GUI).

The `tkinter` library includes a set of themed widgets that we can use in our applications, but in order to use them, we have to import `ttk` from the tkinter module.

``from tkinter import ttk``

## §Modules and namespaces

Now that we've seen two different types of import, it's worth talking about what happens when we import a module, and why we access things in different ways depending on how we import them. For example, why do we need to put this `math.` in front of `pi` when importing it one way, but not the other?

Let's use the same technique as we did in day 13 and look at what gets defined in the global scope when we use the two different import methods.

First, let's try `import math`. We can see what is in the global namespace like so:

``````import math

print(globals())``````

What we get is a dictionary like this:

``````{
'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'main.py',
'__cached__': None,
}``````

Just like before we have a lot of stuff defined here when we start our program, but right at the end we have a name called `math`. Associated with this name `math` we have something called a module object, and in this case we can see that `Python` has found the code for this module somewhere in our Python installation.

When we import a module, Python runs the module and creates this module object so that we can access the things inside of it. In order to indicate we want to do something with the module, we have to first access the module using the name it's assigned to (in this case, `math`), and then we can access the things inside using the dot notation.

Now let's look at what happens when we import specific items from the `math` module. Once again I'm going to use the `pi` and `tau` constants, so now our code is going to look like

``````from math import pi, tau

print(globals())``````

And we get the following output:

``````{
'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'main.py',
'__cached__': None,
'pi': 3.141592653589793,
'tau': 6.283185307179586
}``````

One thing to notice is that we no longer have this `math` module object, and nothing is assigned to the name `math` in the global scope. This is why we're no longer able to access things like `fsum` using something like `math.fsum`.

What we do have are two new names, `pi` and `tau`, and these names are assigned long float values. This is what we're referring to when we use the names `pi` and `tau`. They're just normal variables that Python went ahead and defined for us when we imported the constants.

### §Important

As I mentioned in this section, when we import a module, Python has to run the module. This makes a lot of sense, because some values or functions may rely on other values in the module. No matter what style of import we use, Python always has to run the module.

One thing that a lot of Python developers mistakenly assume is that using an import like this:

``from math import pi, tau``

Is somehow more efficient, because we're only grabbing a couple of values, and Python therefore didn't have to run the module. This isn't the case, so please don't be tempted to use this specific import syntax because you want to make the module load more efficient.

There is, however, one important way that the syntax above can help with efficiency, and that's when referencing the values. If you're using a name many, many times in a performance sensitive part of your application, referring to `pi` directly will be marginally faster than `math.pi`. This is very rarely something you're going to have to worry about though, and the difference in speed is very minor. You need millions of references for there to be any noticeable difference.

## §Aliased imports

Sometimes module names are fairly long, or we just have to refer to them many times, and including the module name whenever we call a function defined in that module can become a little cumbersome. For this reason, we can use an alias for a module when we import it. This allows us to refer to the module using this alias, rather than the module's name.

For example, let's say we're doing some data analysis work and we've decided to use the `numpy` module.

It's common convention when importing numpy to import it using the name `np`, which we can accomplish using the `as` syntax.

``import numpy as np``

What we've done here is set an alias for the `numpy` module, which just means we've asked Python to let us refer to it by some other name. In this case, we've asked to refer to it using `np`.

Let's print what ends up in the global namespace in this case:

``````import numpy as np

print(globals())``````

If you're running this code on repl.it, you're going to get something like the output below. If you're running this in a local Python installation, the code may not work, because `numpy` is a third party module, and has to be installed.

``````{
'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'main.py',
'__cached__': None,
'np': <module 'numpy' from '/home/runner/.local/share/virtualenvs/python3/lib/python3.8/site-packages/numpy/__init__.py'>
}``````

As we can see, we once again get a `module` object, and the module's name is `'numpy'`, but Python has assigned this module to the name `np`.

This means we can access things in the `numpy` module using `np.`, such as the array type. We'd do this by typing `np.array`.

## §Importing using `*`

In addition to the import syntax we've looked at so far, we can also write something like this:

``from math import *``

This syntax essentially means, "Give me everything in the `math` module". We don't quite get everything, as we're still not going to get access to module contents that require a specific import, like when importing `ttk` from `tkinter`.

I want to make it very clear up front that this is not a style of import you should be using in your code 99% of the time. The reason for this is that all of the names defined in the module we're importing are going to get added to the global namespace.

If you want to see this for yourself, run the following code.

``````from math import *

print(globals())``````

This is a big problem, because it can very easily lead to name conflicts. For example, we might accidentally use a name of something defined in a module we've imported, and then we've overwritten the value we got from the module. If different modules also use the same names for values, we're also going to run into issues where one is overwriting the other.

For example, both `math` and `numpy` define a value called `pi`, but only one value can be assigned to the name `pi` in our global namespace.

If it's a bad idea to use this style of import, then why does it exist? It's mostly there to facilitate quick testing. If we're not working on a serious application, using the `*` import can save us a lot of typing where we don't need to worry about best practices.

Just don't use it in any serious applications you're writing unless you have a genuine use case and you know what you're doing.

## §Exercises

1) Import the `fractions` module and create a `Fraction` from the float `2.25`. You can find information on how to create fractions in the documentation.

2) Import only the `fsum` function from the `math` module and use it to find the sum of the following series of floats:

``numbers = [1.43, 1.1, 5.32, 87.032, 0.2, 23.4]``

3) Import the `random` module using an alias, and find a random number between `1` and `100` using the `randint` function. You can find documentation for this function here.

4) Use the `randint` function from the exercise above to create a new version of the guessing game we made in day 8. This time the program should generate a random number, and you should tell the user whether their guess was too high, or too low, until they get the right number.

You can find our solutions to the exercises here.

We don't have anything on `numpy`, but if you want to learn more about this module, there's a good quickstart tutorial page in the `numpy` documentation.
If you're interested in digging into the problems with floating point numbers, and a solution to those problems, you can check out our post comparing floats to `decimal` objects. 